前言:本文只是根据自建的项目所做的文档,文中没有面面俱到,一些小配置并没有在上面写,在实际开发中可能会有写不可预估的出入,希望对各位看官有一定的参考价值
引子:目前社区比较知名的脚手架,有vue-cli、create-react-app,他们都解决了一站式的项目环境配置问题虽然。不过还有一些问题,它们没法解决,比如:1、vue-cli专注的vue项目的环境搭建和配置问题、那每个公司的业务类型不一样,有些公司是金融业务,有些公司是物流业务,如何把相关的东西集成进来?2、每一个公司在项目的沉淀中都是不一样的,比如组件的沉淀、监控埋点方案的沉淀、以及其他样式、目录、service等等一系列编码相关的沉淀,用什么样的方式,能够避免我们的重复劳动? vue-cli 已经非常成熟,成熟到可能自己写的 webpack 性能上不一定比得上vue-cli。当然只是性能上。在实用性,拓展性,可玩性却有很大的操作空间,vue-cli是把饭喂到嘴边,但是并不知道这个饭是如何做的,只见其名不知其意,相比于自己搭建一套环境,你可以知道各个版本的差异化,哪些配置已经废除或者有更好的替代品,不同版本间的环境依赖如何做兼容,哪些方式是最佳实践,对基础业务有更深的理解
关于项目根文件下package.json
中的devDependencies
–save-d和dependencies
–save常见认知:devDependencies
用于本地环境开发。dependencies
用于生产环境。通过NODE_ENV=developement
或NODE_ENV=production
指定开发或生产环境。更多参考
关于全局变量process.env.NODE_ENV
,在webpack5中可以使用new webpack.DefinePlugin({})
来定义一个或者多个不同的全局变量(不是windows),里面的值需要JSON.stringify
包一层。优先级DefinePlugin > --mode(package.json) > mode(webpack.dev或者webpack.prod中定义的)。更多请戳关于环境变量中的命名规则可以借鉴模式与环境变量 1.初始化:npm init
(或者使用默认执行的命令npm init -y
) 根目录创建build——webpack.dev.js,webpack.pord.js,webpack.base.js
根目录创建index.html,也可以在public下创建 根目录创建src——App.vue,main.js
2.安装webpack: npm i webpack webpack-cli -D
(这里默认是最新版本,如果想使用旧版本的话可以用npm i --save-dev webpack@
。不建议全局安装,会将项目中的webpack锁定到指定版本,在使用不同版本的项目中可能会导致构建失败。关于为什么要单独安装webpack-cli——已经从webpack4后版本剥离)
3.安装vue及相关loader: npm i vue@2 -S
,npm i vue-loader -D
(解析和转换vue文件,交给对应的loader处理),npm i vue-template-compiler -D
(编译vue模板的包,传入模板返回AST抽象语法树。vue3替代为@vue/compiler-sfc)
4.样式相关:npm install -D style-loader css-loader postcss-loader autoprefixer sass-loader sass-resources-loader
npm install --save postcss
npm install less less-loader --save
(采用sass与less可供选择。style-loader
与vue-style-loader
之间推荐使用后者)
5.html相关:npm i html-webpack-plugin -D
(在打包结束后,自动生成一个html文件,并把打包生成的js文件引入到这个html文件当中)
6.webpack相关配置:npm install webpack-dev-server -D
(对代码进行热重载)。npm install -D cross-env
(兼容不同平台)。npm i clean-webpack-plugin -D
(在打包之前清空output配置的文件夹)
7.文件类配置:npm install file-loader url-loader -D
{test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,type: 'asset',parser: {dataUrlCondition: {maxSize: 10 * 1024, // 默认是 8kb},},generator: {filename: 'static/images/[name][hash:5][ext]',},exclude: /node_modules/,},{test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,type: 'asset/resource', // 类似 file-loader 导出文件generator: {filename: 'static/media/[name][hash:5][ext]',},exclude: /node_modules/,},// 字体文件{test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,type: 'asset/resource',generator: {filename: 'static/font/[name][hash:5][ext]',},exclude: /node_modules/,}
8.配置文件哈希值:webpack 提供了三种 hash 方式,分别是 hash,chunkhash,contenthash。
①hash 是项目工程级的,整个工程构建的文件 hash都是一样的,所以只要工程文件有一个修改了,那么所有打包文件的,hash都会改变,这明显不利于文件缓存,比如第三方库的 chunk
②chunkhash 和hash不一样,它根据不同的入口文件(Entry)进行依赖文件解析、构建对应的chunk,生成对应的hash值。我们在生产环境里把一些公共库和程序入口文件区分开,单独打包构建,接着我们采用chunkhash的方式生成hash值,那么只要我们不改动公共库的代码,就可以保证其hash值不会受影响
③contenthash 表示由文件内容产生的hash值,内容不同产生的 contenthash值也不一样。在项目中,通常做法是把项目中 css 都抽离出对应的 css 文件来加以引用
配置文件更改:filename:'[name].[contenthash].js'
9.区分运行环境:运行npm run serve
后, dist
目录被清空了。这并不是我们想看到的,加上还有非常多场景需要我们区分开发和生产环境,比如配置 sourceMap
之类的问题。所以在build
文件下再新建webpack.pord.js
文件(生产环境),安装npm i webpack-merge -D
用来合并配置
// 生产环境
const devConfig = require('./webpack.dev')
const { merge } = require('webpack-merge')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = merge(devConfig, {mode: 'production',plugins: [new CleanWebpackPlugin()]
})
10.针对浏览器配置css前缀自动补全:npm i autoprefixer postcss-loader --save
.注意:配置 Autoprefixer
之前,需要先添加Browserslist
。可以在根目录一个.browserslistrc
文件,也可以在package.json
文件中添加:browserslist
。不过一定要加,不然 autoprefixer
不生效。其次,必须在根目录创建一个 postcss.config.js
。
package.json:
"browserslist": ["last 1 chrome version","last 1 firefox version","last 1 safari version", "> 1%", "iOS >= 7", "Android > 4.1"]
11.配置CSS样式分离:npm i mini-css-extract-plugin --save
. 建议先看这篇文章webpack/vue-cli中的 publicPath 区别 loader改成miniCssExtractPlugin
后就不能实现css的热加载 不过开发时也不需要分离css,在生产环境使用即可 webpack.prod.js:
...
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
rules:[
{test: /\.(less|css)$/,use: [{loader: MiniCssExtractPlugin.loader,options: {publicPath: '../' // 注意添加了输出路径}},{ loader: 'css-loader', options: { esModule: false } },'postcss-loader', 'less-loader']},
]plugins: [new MiniCssExtractPlugin({// css的分离到了单独的 css 目录下,并且哈希值继续使用 contenthashfilename: 'css/[name].[contenthash:4].css'})]
12.ES6转ES5:npm i babel-loader @babel/preset-env @babel/core --save
npm install --save core-js@3
webpack.dev.js:
rules: [{test: /\.js$/,use: ['babel-loader'],include: path.resolve(__dirname, '../src'),exclude: /(node_modules)/},]
在根目录新增 .babelrc 文件:
{"presets": [["@babel/preset-env",{"useBuiltIns": "usage","corejs": 3}]]
}
另一个方案是 @babel/plugin-transform-runtime + @babel/runtime + @babel/runtime-corejs3
// .babelrc
{"plugins": ["@babel/plugin-transform-runtime",{corejs: 3}]
}
13.关于项目热更新:较为复杂的方法为依赖于express平台去使用npm i express webpack-hot-middleware webpack-dev-middleware --save
.目前不做详细介绍,坑有些多。有个简单的方法可以满足日常开发需要
plugins:[...new webpack.HotModuleReplacementPlugin(),// 当开启 HMR 的时候使用该插件会显示模块的相对路径,建议用于开发环境new webpack.NoEmitOnErrorsPlugin()
]
devServer: {...hot: true,//是否使用 HMRopen: true,compress: true,//是否开启 gzipproxy: { //代理'/api/': {target: 'http://www.baidu.com',changeOrigin: true}}
},
14.生产环境使用splitChunks
拆分代码:
optimization: {splitChunks: {cacheGroups: {vendor: {priority: 1, // 优先级配置,优先匹配优先级更高的规则,不设置的规则优先级默认为0test: /node_modules/, // 匹配对应文件chunks: 'initial',name: 'vendor',minChunks: 1},commons: {priority: 0,chunks: 'initial',name: 'commons', // 打包后的文件名minChunks: 2,}}}},--------------------------------------------------------------------------如果使用了 html-webpack-plugin,需要添加 chunks 配置来自动引入拆分出的 chunknew HtmlWebpackPlugin({// ...chunks: ['moment','main'],
}),
1.在src文件夹下新建 router,store,views,utils,styles,components
,并安装vuex,vue-router,axios
:npm i vuex --save,npm i vue-router --save,npm i axios --save 路由文件要使用按需加载需安装npm install@babel/plugin-syntax-dynamic-import --save
babelrc文件中配置:
{"plugins": ["@babel/plugin-syntax-dynamic-import"]
}
2.安装eslint:npm install -D eslint eslint-loader @babel/eslint-parser eslint-plugin-vue
在项目根目录新增 .eslintrc.js 配置文件:
module.exports = {root: true,globals: {process: true},parserOptions: {parser: 'babel-eslint',sourceType: 'module',ecmaFeatures: {// 支持装饰器legacyDecorators: true}},env: {browser: true,node: true,es6: true},extends: ['plugin:vue/recommended', 'eslint:recommended'],plugins: ['babel', 'prettier'],rules: {// 使用2个空格缩进indent: ['error',2,{SwitchCase: 1,flatTernaryExpressions: true}],// switch必须提供 default'default-case': 'error',// 禁止一成不变的循环,防止代码出现死循环'no-unmodified-loop-condition': 'error',// 禁止在变量未声明之前使用'no-use-before-define': 'error',// 代码后不使用分号semi: ['error', 'never'],// 注释 // 或 /* 之后必须有一个空格'spaced-comment': ['error', 'always'],// 禁止重复导入模块,对于同一模块内内容,应一次导入'no-duplicate-imports': 'error',// 必须使用let 或 const, 不能使用var'no-var': 'error',// 要求大括号内必须有空格'object-curly-spacing': ['error', 'always'],// 数组前后不需要添加空格'array-bracket-spacing': ['error', 'never'],// 箭头函数前后必须要有空格'arrow-spacing': ['error',{before: true,after: true}],// 代码中可出现console'no-console': 'off',// 正则中可以出现控制字符'no-control-regex': 'off','no-unused-vars': ['error',{ignoreRestSiblings: true,// 可以声明未使用的h,方便jsxargsIgnorePattern: 'h'}],// 行注释必须在行上面'line-comment-position': ['error', { position: 'above' }],// 组件名称必须是大驼峰'vue/name-property-casing': ['error', 'PascalCase'],// vue Html元素单标签关闭方式'vue/html-self-closing': ['error',{html: { normal: 'never', void: 'always' },svg: 'always',math: 'always'}],// 组件在template内必须使用 kebab-case 格式'vue/component-name-in-template-casing': ['error','kebab-case',{registeredComponentsOnly: false,ignores: []}],// template 内必须使用 ==='vue/eqeqeq': 'error',// 允许使用v-html'vue/no-v-html': 0,// 禁用隐式的eval() 比如 setTimeout('alert();', 100)'no-implied-eval': 'error'},
}
.eslintignore 文件可以忽略那些不用语法检查的文件:
node_modules
/dist
3.安装element-ui按照官网走便是,值得一提的是引入样式文件可能会与dev文件配置的属性有冲突,把exclude: /(node_modules)/
删掉即可。另个是关于官网上写的babel转es2015的问题,可以将 "presets": [["es2015", { "modules": false }]]
中的es2015换成已经配置的@babel/preset-env
。全部引入和按需引入只能存在一个
resolve: {alias: { //配置别名可以加快搜索速度'@': path.resolve(__dirname, '../src'),},extensions: ['.js', '.jsx', '.ts', '.tsx', '.json', '.vue', '.sass', '.less'],//指定 extensions 之后,使用require 和 import 的时候就不需要加文件扩展名了,查找的时候会依次匹配,但同一个目录有不同类型的同名文件时,也只会匹配第一个modules: [ //指定第三方依赖的存放目录,默认为'node_modules'path.resolve(__dirname, '../node_modules'),'node_modules']},
缓存
1. //webpack.dev.js,缓存之前打包的内容,配置之后会生成一个.cash文件夹,通过文件缓存,直接缓存到本机磁盘
cache: {type: 'filesystem', //默认缓存到 node_modules/.cache/webpack。还可以使用 cacheDirectory选项自定义配置
},
2.babel-loader 自带缓存配置。开启后会将缓存放在node_modules/.cache/babel-loader
{loader: 'babel-loader',options: {cacheDirectory: true,}
}
3.使用此npm i cache-loader -D,可以将其他 loader 的结果缓存到磁盘中,默认路径node_modules/.cache/cache-loader
{test: /\.css$/,use: ['cache-loader','style-loader','css-loader'],
},
JS 压缩
npm i terser-webpack-plugin -D
// webpack.prod.js
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {optimization: {minimize: true,minimizer: [new TerserPlugin({ // 压缩JS代码terserOptions: {compress: {drop_console: true, // 去除console},},})],},
};
HTML 压缩
1.html-webpack-plugin的 minify 选项用于设置html文件的压缩
minify: {removeComments: true,collapseWhitespace: true,removeRedundantAttributes: true,useShortDoctype: true,removeEmptyAttributes: true,removeStyleLinkTypeAttributes: true,keepClosingSlash: true,minifyJS: true,minifyCSS: true,minifyURLs: true,},
2.使用 html-minifier-terser,webpack5 开始,像压缩类的插件,应该配置在 optimization.minimizer 数组中,方便统一管理
npm i html-minimizer-webpack-plugin -D
// webpack.prod.js
const HtmlMinimizerPlugin = require("html-minimizer-webpack-plugin");
optimization: {minimize: true,minimizer: [....new HtmlMinimizerPlugin(),],
}
CSS 压缩
npm i css-minimizer-webpack-plugin -D
// webpack.prod.js
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = {optimization: {// 告知 webpack 使用 TerserPlugin 或其它在 optimization.minimizer 定义的插件压缩 bundle。minimizer: [...new CssMinimizerPlugin(),],},
};
构建时间优化`thread-loader`:多进程打包,可以大大提高构建的速度,使用方法是将thread-loader放在比较费时间的loader之前,比如babel-loader
npm i thread-loader -D
{test: /\.js$/,use: ['thread-loader','babel-loader'],}
}
exclude & include
exclude:不需要处理的文件
include:需要处理的文件{test: /\.js$/,//使用include来指定编译文件夹include: path.resolve(__dirname, '../src'),//使用exclude排除指定文件夹exclude: /node_modules/,use: ['babel-loader']},
source-map 产生的文件映射了压缩后的代码所对应的转换前的源代码位置,解决了代码压缩后难以调试的问题
// webpack.dev.js开发环境
module.exports = {mode: 'development',devtool: 'eval-cheap-module-source-map'
}
// webpack.prod.js生产环境
module.exports = {mode: 'production',devtool: 'nosources-source-map'
}
压缩打包的文件
npm i compression-webpack-plugin -D
// webpack.prod.js
const CompressionPlugin = require('compression-webpack-plugin')
new CompressionPlugin({algorithm: 'gzip',threshold: 10240,test: /\.(js|css|json|txt|html|ico|svg)(\?.*)?$/i,minRatio: 0.8,deleteOriginalAssets: true })
打包体积分析
npm i webpack-bundle-analyzer -D
// webpack.prod.js
const { BundleAnalyzerPlugin} = require('webpack-bundle-analyzer')plugins: [new BundleAnalyzerPlugin(),
]
小图片转base64
webpack5中url-loader已被废弃,改用asset-module
{test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,type: 'asset',parser: {dataUrlCondition: {maxSize: 25 * 1024, // 默认是 8kb},},generator: {filename: 'static/images/[name][hash:5][ext]',},exclude: /node_modules/,},
构建进度条
npm i progress-bar-webpack-plugin -D 或者npm i webpackbar -D
// webpack.base.js
const ProgressBarPlugin = require('progress-bar-webpack-plugin')
plugins: [...new ProgressBarPlugin() 或 new WebpackBar(),],
自适应布局解决方案
npm install postcss postcss-pxtorem@5.1.1 --save-dev (vue2项目不宜装太高版本)
npm i lib-flexible-computer --save-dev
这里要说明lib-flexible-computer这个依赖,大部分pc的适配都是使用的npm i lib-flexible -S,这个依赖,做适配的话只能做到屏幕540一下的,pc端使用并不是很好用
在util文件夹下创建rem.js
(function flexible (window, document) {var docEl = document.documentElementvar dpr = window.devicePixelRatio || 1function setBodyFontSize () {if (document.body) {document.body.style.fontSize = 12 * dpr + 'px'} else {document.addEventListener('DOMContentLoaded', setBodyFontSize)}}setBodyFontSize()function setRemUnit () {var rem = docEl.clientWidth / 10docEl.style.fontSize = rem + 'px'}setRemUnit()window.addEventListener('resize', setRemUnit)window.addEventListener('pageshow', function (e) {if (e.persisted) {setRemUnit()}})if (dpr >= 2) {var fakeBody = document.createElement('body')var testElement = document.createElement('div')testElement.style.border = '.5px solid transparent'fakeBody.appendChild(testElement)docEl.appendChild(fakeBody)if (testElement.offsetHeight === 1) {docEl.classList.add('hairlines')}docEl.removeChild(fakeBody)}
})(window, document)
main.js中引入:import "lib-flexible-computer";import '@/utils/rem'
postcss.config.js中配置:
module.exports = {plugins: {'autoprefixer': require('autoprefixer'),'postcss-pxtorem': {rootValue: 192,propList: ['*']}}
}
package.json中添加:
"postcss": {"plugins": {"autoprefixer": {"overrideBrowserslist": ["Android 4.1","iOS 7.1","Chrome > 31","ff > 31","ie >= 8"]},"postcss-pxtorem": {"rootValue": 192,"propList": ["*"]}}
}
==提示==:1.vue2不要安装最新的vue-loader,15即可
2.关于webpack-dev-server
中的devServer
配置,在5版本中弃用了一些属性,比如说contentBase:""
更改为static:{}
,图中是新属性供参考
3.css热更新不生效:①.webpack内解决:css-loader 4.0
后默认对 esModule
设置的是true
,而 vue-style-loader4.1.0
默认接收的是commonjs
的结果,也就是默认接收的是“css-loader
中esModule
设置的是false
的结果”,导致样式无法加载 vue-cli内解决:[cli.vuejs.org/zh/config/#…](https://link.juejin.cn/?target=https%3A%2F%2Fcli.vuejs.org%2Fzh%2Fconfig%2F%23css-extract “https://cli.vuejs.org/zh/config/#css-extract”” style=“margin: auto” />
4.关于autoprefixer可能要进行降级处理
5.使用webpack-merge后,两个环境的配置不要相同
6.在vue2中使用vuex与vue-router不要默认安装最新版本,应使用3版本。路由模式history开启需要后端支持,前端独自开启会显示页面不存在
7.如果想使用@
导入组件显示路径的话,可以如下配置:打开文件 - 首选项 - 设置 - 搜索 Path Intellisense - 打开 settings.json ,添加:
"path-intellisense.mappings": { "@": "${workspaceRoot}/src"
}
在vue项目 package.json 所在同级目录下创建文件 jsconfig.json:
{"compilerOptions": {"target": "ES6","module": "commonjs","allowSyntheticDefaultImports": true,"baseUrl": "./","paths": {"@/*": ["src/*"]}},"exclude": ["node_modules"]
}
8.sass变量导出undefined问题记录
主要是在css-loader版本较新的情况下才会需要以.module.scss结尾
{test: /\.s[ac]ss$/i,use: ["style-loader",{loader: "css-loader",options: {modules: {namedExport: false,},},},'sass-loader']
}