mkdir vue3-spa-template
cd vue3-spa-template
npm init -y
npm install webpack webpack-cli --save-dev
备注:在安装一个 package时,此 package 要打包到生产环境中时,你应该使用
npm install --save
。此package只用于开发环境时(例如,linter, 测试库等),你应该使用npm install --save-dev
npm install --save lodash
备注:执行
npx webpack
,会将我们的脚本src/index.js
作为 入口起点,会生成dist/main.js
作为 输出,所以我们index.html模板这里的js路径要先写成main.js。
npx webpack
,会在dist文件夹下生成打包的main.js文件,在浏览器中打开index.html如下所示。webpack.config.js
npx webpack --config ./build/webpack.config.js
后效果和第3步手动执行的结果一样,生成main.js 。输入一大段字符来运行webpack打包程序无疑是繁琐的,所以我们在package.json中配置script脚本如下,这样我们就可以使用
npm run build
来执行打包动作了
参考链接:https://webpack.docschina.org/guides/getting-started/
前端主流做法是将输出的打包文件命名为
bundle
,因此,我们将上述html
及webpack.config.js
中的main.js
更名为bundle.js
为了在 JavaScript 模块中 import
一个 CSS 文件,你需要安装 style-loader 和 css-loader,并在 module
配置 中添加这些 loader。
npm install style-loader css-loader --save-dev
备注:应保证 loader 的先后顺序:
'style-loader'
在前,而'css-loader'
在后。
备注:这里css样式文件会被打包到bundle.js中,而不是插入到index.html中
在 webpack 5 中,可以使用内置的资源模块( Asset Modules),它是一种模块类型,它允许使用资源文件(字体,图标等)而无需配置额外 loader。
在 webpack 5 之前,通常使用:
raw-loader
将文件导入为字符串url-loader
将文件作为 data URI 内联到 bundle 中file-loader
将文件发送到输出目录资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些 loader:
asset/resource
发送一个单独的文件并导出 URL。之前通过使用file-loader
实现。asset/inline
导出一个资源的 data URI。之前通过使用url-loader
实现。asset/source
导出资源的源代码。之前通过使用raw-loader
实现。asset
在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用url-loader
,并且配置资源体积限制实现。
修改index.js测试打包后结果:
字体文件的加载同图片资源一样,同样使用内置的资源模块( Asset Modules)进行加载,无需额外配置loader。
添加字体文件并引用测试
到目前为止,我们都是在
index.html
文件中手动引入所有资源,然而随着应用程序的不断增长,一旦开始 使用哈希值进行文件命名 并输出 多个 bundle,手动管理index.html
文件将变得困难。然而,使用一些插件可以让这个过程更容易管理。
npm install --save-dev html-webpack-plugin
build/webpack.config.js
文件,利用public文件夹下的内容通过配制自动生成dist/index.html模版:你可能已经注意到,由于遗留了之前的指南的代码示例,
/dist
文件夹显得相当杂乱。webpack 将生成文件并放置在/dist
文件夹中,但是它不会追踪哪些文件是实际在项目中用到的。通常比较推荐的做法是,在每次构建前清理
/dist
文件夹,这样只会生成用到的文件。可以使用output.clean
配置项实现这个需求。
参考链接:https://webpack.docschina.org/guides/output-management/
mode
区分开发环境 development
和 生产环境 production
当 webpack 打包源代码时,可能会很难追踪到 error(错误)和 warning(警告)在源代码中的原始位置。
为了更容易地追踪 error 和 warning,JavaScript 提供了 source map 功能,可以将编译后的代码映射回原始源代码。source map 会直接告诉你错误来源于哪一个源代码。
可用选项参考:https://webpack.docschina.org/configuration/devtool
webpack-dev-server
提供了一个基本的 web server,并具有实时重新加载的功能。更多配置文档:https://webpack.docschina.org/configuration/dev-server
webpack-dev-server
npm install --save-dev webpack-dev-server
webpack.config.js
此时可以直接运行 npm run dev
开发需求了,并且在每一次代码变更后都会自动重新编译
参考链接:https://webpack.docschina.org/guides/development/
SplitChunksPlugin
插件可以将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk。
一旦开始分离代码,一件很有帮助的事情是,分析输出结果来检查模块在何处结束。webpack-bundle-analyzer:一个 plugin 和 CLI 工具,它将 bundle 内容展示为一个便捷的、交互式、可缩放的树状图形式。我们利用该插件分析打包情况。
npm install --save-dev webpack-bundle-analyzer
build/webpack.config.js
,添加插件配置参考链接:https://webpack.docschina.org/guides/code-splitting/
随着webpack.js的配置越来越多,有些配置我们希望只在开发环境上生效,有些配置我们希望只在生产环境上生效,所以是时候引入环境变量进行控制了。
cross-env:它是运行跨平台设置和使用环境变量的脚本(解决了,使用NODE_ENV =production, 来设置环境变量时windows环境下报错的问题)
npm install --save-dev cross-env
通过配置确保 webpack 编译生成的文件在没有改变时能够被客户端缓存,而在文件内容变化后,又能够请求到新的文件。
[contenthash]
[contenthash]
将根据资源内容创建唯一哈希值。当资源内容发生变化时,[contenthash]
也会发生变化。
contenthash
并截取保留8位按照官网提示,以防万一的情况,我们配置
引导提取模板
来规避上述问题
正如我们在 代码分离 中所学到的,
SplitChunksPlugin
可以用于将模块分离到单独的 bundle 中。webpack 还提供了一个优化功能,可以使用optimization.runtimeChunk
选项将 runtime 代码拆分为一个单独的 chunk。将其设置为single
以为所有 chunk 创建一个 runtime bundle实际上前边为了看到打包前后文件体积大小的差异,我们进行了多入口的实验
将第三方库(library)(例如 lodash
或 vue)提取到单独的 vendor
chunk 文件中,是比较推荐的做法,这是因为,它们很少像本地的源代码那样频繁修改。通过SplitChunksPlugin
插件的 cacheGroups
选项来实现抽取,现修改build/webpack.config.js文件如下:
执行 npm run build
后会将以使用的lodash抽取到vendors.hash.bundle.js
中
参考链接:https://webpack.docschina.org/guides/caching/
实际开发过程中我们通常是使用css预处理器来书写css样式
npm install less less-loader --save-dev
build/webpack.config.js
文件如下:src/style.css
为src/style.less
,并在修改引用后进行build在编写css样式的时候,浏览器的兼容性是我们应该考虑的一个问题,为此我们使用postcss、postcss-loader、 postcss-preset-env进行样式兼容处理。
npm install --save-dev postcss-loader postcss postcss-preset-env
webpack.config.js
如下:参考链接:
https://webpack.docschina.org/loaders/less-loader/
https://webpack.docschina.org/loaders/postcss-loader/
本插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载。
本插件基于 webpack v5 的新特性构建,并且需要 webpack 5 才能正常工作。
npm install --save-dev mini-css-extract-plugin
npm install css-minimizer-webpack-plugin --save-dev
参考链接:
https://webpack.docschina.org/plugins/mini-css-extract-plugin/
https://webpack.docschina.org/plugins/css-minimizer-webpack-plugin/
npm install -D babel-loader @babel/core @babel/preset-env
npm install -D @babel/plugin-transform-runtime
Babel 在每个文件都插入了辅助代码,使代码体积过大
Babel 对一些公共方法使用了非常小的辅助代码,比如
_extend
。默认情况下会被添加到每一个需要它的文件中。你可以引入 Babel runtime 作为一个独立模块,来避免重复引入。
以下版本:默认情况下,Webpack假定你的目标环境支持ES2015的一些特性
“@babel/core”: “^7.22.9”,
“@babel/plugin-transform-runtime”: “^7.22.9”,
“@babel/preset-env”: “^7.22.9”,
“babel-loader”: “^9.1.3”,
“webpack”: “^5.88.2”,
“webpack-bundle-analyzer”: “^4.9.0”,
“webpack-cli”: “^5.1.4”,
“webpack-dev-server”: “^4.15.1”
方式一:当前版本下,webpack5配置babel将不会转换箭头函数语法、const语法等ES6语法(默认targets为default)
需要显式设置targets: 'ie 11’等,才会转化为ES5语法
例:如果你不想要箭头函数,那么就这样做
// webpack.config.js
module.exports = {
// ...
output: {
// ...
environment: {
// ...
arrowFunction: false, // 不要输出箭头函数
},
},
};
参考链接:https://webpack.js.org/loaders/babel-loader/#install
webpack v5 开箱即带有最新版本的
terser-webpack-plugin
。如果你使用的是 webpack v5 或更高版本,同时希望自定义配置,那么仍需要安装terser-webpack-plugin
。
按照官网的说法,我们实际上不需要去进行特殊的配置了,但是我们在配置CSS代码压缩时有主意到:
因此,我们修改webpack配置如下:
图片压缩分为无损压缩
和有损压缩
,它们共同要使用的包是:image-minimizer-webpack-plugin
和 imagemin
npm install image-minimizer-webpack-plugin imagemin --save-dev
npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo --save-dev
npm install @squoosh/lib --save-dev
我们通过以上实验发现,在压缩图片后确实可以达到减少 文件大小
的目的,但是也有相应的弊端:打包 时长过长
,因此,在使用时要根据实际情况来抉择。
参考链接:https://webpack.js.org/plugins/image-minimizer-webpack-plugin/
现在,webpack 将按照默认条件,自动地在
resource
和inline
之间进行选择:小于 8kb 的文件,将会视为inline
模块类型,否则会被视为resource
模块类型。(type需要设置为:asset,不能指定为asset/resouce)可以通过在 webpack 配置的 module rule 层级中,设置
Rule.parser.dataUrlCondition.maxSize
选项来修改此条件:
npm install vue-loader -D
npm install vue vue-router vuex element-plus --save
// src/index.js
import { createApp } from "vue";
import App from "./App.vue";
const app = createApp(App);
console.log(app);
app.mount("#app");
// src/App.vue
{{ welcome }}
// src/index.js
import { createApp } from "vue";
import router from "./router";
import App from "./App.vue";
const app = createApp(App);
app.use(router);
app.mount("#app");
// src/router/index.js
const { createRouter, createWebHashHistory } = require("vue-router");
const routes = [
{
path: "/home",
name: "Home",
component: () => import("../views/Home.vue"),
},
];
const router = createRouter({
history: createWebHashHistory(),
routes,
});
export default router;
Home Page
{{ welcome }}
Home
// src/index.js
import { createApp } from "vue";
import router from "./router/index";
import App from "./App.vue";
import { createPinia } from "pinia";
const app = createApp(App);
app.use(createPinia());
app.use(router);
app.mount("#app");
import { defineStore } from "pinia";
// 第一个参数是应用程序中 store 的唯一 id
export const useUser = defineStore("useUser", {
state: () => {
return {
age: 18,
name: "小华",
};
},
getters: {
// 自动将返回类型推断为数字
fatherAge(state) {
return state.age + 18;
},
},
// 相当于vue中的methods,可以是异步的
actions: {
addUserAge() {
this.age++;
},
},
});
{{ welcome }}
Home {{ user.name }} {{ user.age }}
Home Page
姓名:{{ name }}
年龄:{{ age }}
父亲年龄:{{ fatherAge }}
npm install -D unplugin-vue-components unplugin-auto-import
// webpack.config.js
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')
module.exports = {
// ...
plugins: [
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
}
// src/index.js
import { createApp } from "vue";
import router from "./router/index";
import App from "./App.vue";
import { createPinia } from "pinia";
import ElementPlus from "element-plus";
const app = createApp(App);
app.use(createPinia());
app.use(ElementPlus, { size: "middle", zIndex: 3000 });
app.use(router);
app.mount("#app");
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aKrEkCKO-1690792169180)(/Users/liangchenyang/Documents/Web/Vue模板项目(脚手架)/从头搭建一个基于webpack的Vue3项目.assets/image-20230731141103144.png)]
const webpack = require("webpack");
module.exports = {
// ...
plugins: [
new webpack.DefinePlugin({
// 解决浏览器报的warning
__VUE_OPTIONS_API__: true, // 是否支持vue2的optionsAPI
__VUE_PROD_DEVTOOLS__: false, // 开发阶段tree shaking
}),
],
}
npm i pinia-plugin-persistedstate -D
// src/index.js
import { createApp } from "vue";
import router from "./router/index";
import App from "./App.vue";
import { createPinia } from "pinia";
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
import ElementPlus from "element-plus";
const app = createApp(App);
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
app.use(pinia);
app.use(ElementPlus, { size: "middle", zIndex: 3000 });
app.use(router);
app.mount("#app");
import { defineStore } from "pinia";
// 第一个参数是应用程序中 store 的唯一 id
export const useUser = defineStore("useUser", {
state: () => {
return {
age: 18,
name: "小华",
};
},
getters: {
// 自动将返回类型推断为数字
fatherAge(state) {
return state.age + 18;
},
},
// 相当于vue中的methods,可以是异步的
actions: {
addUserAge() {
this.age++;
},
},
// 持久化具体配置查看:https://prazdevs.github.io/pinia-plugin-persistedstate/guide/
persist: {
storage: sessionStorage, // 默认被存储在localstorage中
},
});
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6cAdNrYT-1690792169180)(/Users/liangchenyang/Documents/Web/Vue模板项目(脚手架)/从头搭建一个基于webpack的Vue3项目.assets/image-20230731151144349.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JQ1iFscp-1690792169180)(/Users/liangchenyang/Documents/Web/Vue模板项目(脚手架)/从头搭建一个基于webpack的Vue3项目.assets/image-20230731154858432.png)]
npm install eslint -D
npx install-peerdeps --dev eslint-config-airbnb-base
npm install --save-dev eslint-plugin-vue
npm install prettier eslint-config-prettier -D
eslint-plugin-prettier
npm install --save-dev eslint-plugin-prettier
// 解决代码中存在的问题避免错误
module.exports = {
// 规则到此不往上找
root: true,
// env环境设置,预定义全局变量,这些环境并不是互斥的,所以你可以同时定义多个
env: {
browser: true,
node: true,
es2021: true,
},
// 扩展风格,extends属性值可以省略包名的前缀 eslint-config-
extends: [
'plugin:vue/vue3-essential',
'airbnb-base',
// 'prettier', // eslint-config-prettier 来关掉 (disable) 所有和 Prettier 冲突的 ESLint 的配置,prettier 一定要是最后一个,才能确保覆盖
'plugin:prettier/recommended', // 同时使用了 eslint-plugin-prettier 和 eslint-config-prettier 可以这么配置
],
settings: {
'import/resolver': { webpack: { config: 'build/webpack.config.js' } },
},
// 针对个别文件设置新的检查规则
// 要为特定类型的文件指定处理器,请使用 overrides 键和 processor 键的组合
// 若要禁用一组文件的配置文件中的规则,请使用 overrides 和 files
overrides: [],
parserOptions: {
// 指定要使用的 ECMAScript 版本
ecmaVersion: 'latest',
// 设置为 script (默认) 或 module(如果你的代码是 ECMAScript 模块)
sourceType: 'module',
},
// 插件名称可以省略 eslint-plugin- 前缀。插件可以处理除js外的别的文件格式,引入插件的目的就是为了增强 ESLint 的检查能力和范围。
plugins: ['vue', 'prettier'],
// 规则列表
rules: {
// 提醒你不要直接修改函数的入参,为这个规则添加一个白名单,即指定的入参名称不予限制
// 'no-param-reassign': [
// 'error',
// {
// props: true,
// ignorePropertyModificationsFor: [
// 'e', // for e.returnvalue
// 'ctx', // for Koa routing
// 'req', // for Express requests
// 'request', // for Express requests
// 'res', // for Express responses
// 'response', // for Express responses
// 'state', // for vuex state
// ],
// },
// ],
// 提醒你不要直接修改函数的入参
'no-param-reassign': 0,
// .vue文件必须是多个字符
'vue/multi-word-component-names': 0,
// 禁止变量声明覆盖外层作用域的变量
'no-shadow': ['error', { allow: ['state', 'getters'] }],
},
}
# ESLint 总是忽略 /node_modules/ 和 /bower_components/ 中的文件;
# 因此对于一些目前解决不了的规则报错,但是如果又急于打包上线,在不影响运行的情况下,我们就可以利用 .eslintignore 文件将其暂时忽略。
#本地查看dist时,eslint不需要检查
dist
{
"editor.formatOnSave": false,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"eslint.alwaysShowStatus": true
}
**/*.svg
node_modules
dist
//Prettier 支持可以配置参数不多,2022.12.1日查看时,总共才 21 个,这里是所有参数的说明 prettier options
//参考地址:https://prettier.io/docs/en/options.html#print-width
//该文件修改,要重新启动vscode才能生效,否则不生效
//解决代码风格的的问题
module.exports = {
printWidth: 120, //(默认值)单行代码超出 80 个字符自动换行
tabWidth: 4, //默认一个 tab 键缩进相当于 2 个空格
useTabs: true, // 行缩进使用 tab 键代替空格
semi: false, //(默认值)语句的末尾加上分号
singleQuote: true, // 使用单引号
quoteProps: 'as-needed', //(默认值)仅仅当必须的时候才会加上双引号
jsxSingleQuote: true, // 在 JSX 中使用单引号
trailingComma: 'all', // 不用在多行的逗号分隔的句法结构的最后一行的末尾加上逗号
bracketSpacing: true, //(默认值)在括号和对象的文字之间加上一个空格
bracketSameLine: false, // (默认值)元素的 > 单独占一行
arrowParens: 'avoid', // 当箭头函数中只有一个参数的时候可以忽略括弧
htmlWhitespaceSensitivity: 'ignore', // vue template 中的结束标签结尾尖括号掉到了下一行
vueIndentScriptAndStyle: false, //(默认值)对于 .vue 文件,不缩进