目前最流行的前端打包工具-webpack
简介
webpack 是一种前端资源构建工具,一个静态模块打包器(module bundler)。
核心概念
Entry
入口(Entry
)指示 webpack
以哪个文件为入口起点开始打包,分析构建内部依赖图。
Output
输出(Output
)指示 webpack
打包后的资源 bundles
输出到哪里去,以及如何命名。
Loader
Loader
让 webpack
能 够 去 处 理 那 些 非 JavaScript
文 件 (webpack
自 身 只 理 解 JavaScript
)
Plugins
插件(Plugins
)可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩, 一直到重新定义环境中的变量等。
Mode
模式(Mode
)指示 webpack
使用相应模式的配置。
常见配置
安装
npm initnpm install webpack webpack-cli -g 全局安装webpacknpm install webpack webpack-cli -D
配置
无配置打包
开发环境指令:webpack src/js/index.js -o build/js/built.js --mode=development
功能:webpack
能够编译打包 js
和 json
文件,并且能将 es6
的模块化语法转换成 浏览器能识别的语法。
生产环境指令:webpack src/js/index.js -o build/js/built.js --mode=production
配置环境
webpack
是node
写出来,所以需要使用node
的语法
即引入文件的时候使用CommonJS
规范的require
引入,而不能使用ES6
的import
引入。
loader
loader
执行顺序从下到上,从右到左。
css
如果要在打包中用到css
就需要使用到这个loader
。
npm i css-loader style-loader -Dnpm install --save-dev mini-css-extract-plugin css-loader
test: /\.css$/,// 使用哪些 loader 进行处理use: [ // use 数组中 loader 执行顺序:从右到左,从下到上 依次执行 // 创建 style 标签,将 js 中的样式资源插入进行,添加到 head 中生效 'style-loader', // 将 css 文件变成 commonjs 模块加载 js 中,里面内容是样式字符串 'css-loader']
module: { rules: [ { test: /\.css$/, use: [ // 创建 style 标签,将样式放入 // 'style-loader', // 这个 loader 取代 style-loader。作用:提取 js 中的 css 成单独文件 MiniCssExtractPlugin.loader, // 将 css 文件整合到 js 文件中 'css-loader' ] } ]}plugins: [ new HtmlWebpackPlugin({ template: './src/index.html' }), new MiniCssExtractPlugin({ // 对输出的 css 文件进行重命名 filename: 'css/built.css' })],
兼容性处理
npm install --save-dev postcss-loader postcss-preset-env
use: [ MiniCssExtractPlugin.loader, 'css-loader', { loader: 'postcss-loader', options: { ident: 'postcss', plugins: () => [ // postcss 的插件 require('postcss-preset-env')() ] } }]
修改package.json
"browserslist": { "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ], "production": [ ">0.2%", "not dead", "not op_mini all" ]}
提取成单独文件。
npm install --save-dev mini-css-extract-plugin
less
如果想在项目中使用less
就需要这个loader
。
npm i less-loader less -D
图片资源
打包的时候引用到的图片的打包方式。
npm install --save-dev html-loader url-loader file-loader
test: /\.(jpg|png|gif)$/loader: "url-loader",options: { // 图片大小小于 8kb,就会被 base64 处理 // 优点: 减少请求数量(减轻服务器压力) // 缺点:图片体积会更大(文件请求速度更慢) limit: 8 * 1024, // 问题:因为 url-loader 默认使用 es6 模块化解析,而 html-loader 引入图片是 commonjs // 解析时会出问题:[object Module] // 解决:关闭 url-loader 的 es6 模块化,使用 commonjs 解析 esModules: false, // 给图片进行重命名 // [hash:10]取图片的 hash 的前 10 位 // [ext]取文件原来扩展名 name: "[hash:10].[ext]"}
其他资源
即除了制定的资源以外的资源的打包方式。
{ // 排除 css/js/html 资源 exclude: /\.(css|js|html|less)$/, loader: 'file-loader', options: { name: '[hash:10].[ext]' }}
plugins 插件
HTML
npm install --save-dev html-webpack-plugin
// plugins 的配置// html-webpack-plugin// 功能:默认会创建一个空的 HTML,自动引入打包输出的所有资源(JS/CSS)// 需求:需要有结构的 HTML 文件new HtmlWebpackPlugin({ // 复制 './src/index.html' 文件,并自动引入打包输出的所有资源(JS/CSS) template: './src/index.html'})
压缩
new HtmlWebpackPlugin({ template: './src/index.html', // 压缩 html 代码 minify: { // 移除空格 collapseWhitespace: true, // 移除注释 removeComments: true }})
压缩CSS
npm install --save-dev optimize-css-assets-webpack-plugin
使用方法:
直接在插件处添加:
new OptimizeCssAssetsWebpackPlugin()
js
语法检查eslint
,如果要多人合作开发项目或者开发一个大型项目,eslint
非常有必要,它不仅可以检测出代码中潜在的一些BUG
,还可以将代码的风格进行统一。
npm install --save-dev eslint-loader eslint eslint-config-airbnb-base eslint-plugin-import
/*语法检查: eslint-loader eslint注意:只检查自己写的源代码,第三方的库是不用检查的设置检查规则:package.json 中 eslintConfig 中设置~"eslintConfig": {"extends": "airbnb-base"}airbnb --> eslint-config-airbnb-base eslint-plugin-import eslint*/{ test: /\.js$/, exclude: /node_modules/, loader: 'eslint-loader', options: { // 自动修复 eslint 的错误 fix: true }}
修改package.json
"eslintConfig": { "extends": "airbnb-base", "env": { "browser": true }
兼容性处理
在不同的浏览器中,js支持的语法可能也不相同,所以可以指示 babel 做怎么样的兼容性处理。
npm install --save-dev babel-loader @babel/core @babel/polyfill core-js @babel/preset-env
{ test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', options: { // 预设:指示 babel 做怎么样的兼容性处理 presets: [ [ '@babel/preset-env', { // 按需加载 useBuiltIns: 'usage', // 指定 core-js 版本 corejs: { version: 3 }, // 指定兼容性做到哪个版本浏览器 targets: { chrome: '60', firefox: '60', ie: '9', safari: '10', edge: '17' } } ] ] }}
生产环境会自动压缩js代码
每次打包自动删除
npm install --save-dev clean-webpack-plugin
devserver
如果使用webpack
,则每次修改了代码就需要重新进行打包,但是devserver
可以实现在你修改代码之后,自动为你打包运行,并且可以模拟出一个服务器。
运行:
npx webpack-dev-server
使用:
devServer: { // 项目构建后路径 contentBase: resolve(__dirname, 'build'), // 启动 gzip 压缩 compress: true, // 端口号 port: 3000, // 自动打开浏览器 open: true}
环境优化
HMR
HMR:hot module replacement
热模块替换/模块热替换作用:一个模块发生变化,只会重新打包这一个模块(而不是打包所有模块)极大提升构建速度。- 样式文件:可以使用
HMR
功能:因为style-loader
内部实现了。 。js
文件:默认不能使用HMR
功能。html
文件:默认不能使用HMR
功能.同时会导致问题:html
文件不能热更新了解决:修改entry
入口,将html
文件引入。
source-map
devtool: "eval-source-map"
:一种提供源代码到构建后代码映射技术,即如果代码出现报错,是否会指向源代码中错误的哪一行。
source-map
:外部
- 错误代码准确信息和源代码的错误位置
inline-source-map
:内联
- 只生成一个内联source-map
- 错误代码准确信息和源代码的错误位置
hidden-source-map
:外部
- 错误代码错误原因,但是没有错误位置
- 不能追踪源代码错误,只能提示到构建后代码的错误位置
eval-source-map
:内联
- 每一个文件都生成对应的source-map,都在eval错误代码准确信息和源代码的错误位置
nosources-source-map
:外部
- 错误代码准确信息,但是没有任何源代码信息
cheap-source-map
:外部
- 错误代码准确信息和源代码的错误位置只能精确到行
cheap-module-source-map
:外部
- 错误代码准确信息和源代码的错误位置
开发环境
速度快,调试更友好
速度快(eval>inline>cheap>...
)
eval-cheap-souce-map
eval-source-map
调试更友好
souce-map
cheap-module-souce-map
cheap-souce-map
推荐选择
eval-source-map
:调试最友好(React、Vue默认使用)。eval-cheap-module-souce-map
:性能更友好。
生产环境
源代码要不要隐藏?调试要不要更友好
内联会让代码体积变大,所以在生产环境不用内联。
nosources-source-map
全部隐藏
hidden-source-map
只隐藏源代码,会提示构建后代码错误信息
推荐选择
- source-map:调试最友好(推荐生成环境使用)。
- cheap-module-souce-map:速度快。
缓存
cacheDirectory:true
文件资源缓存
hash
:每次wepack构建时会生成一个唯一的hash值。
- 问题:因为js和css同时使用一个hash值。
- 如果重新打包,会导致所有缓存失效。(可能我却只改动一个文件)
chunkhash
:根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样
- 问题:js和css的hash值还是一样的
- 因为css是在js中被引入的,所以同属于一个chunk
contenthash
:根据文件的内容生成hash值。不同文件hash值一定不一样
tree shaking
去除应用程序中没有使用的代码
- 必须使用
ES6
模块化 - 开启
production
环境
减少代码体积
在package.json
中配置
"sideEffects":false
所有代码都是没有副作用的代码,都可以进行tree shaking
可能会把css/@babel/polyfill
去除
code split
- 可以将node_modules中代码单独打包一个chunk最终输出
- 自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk
optimization:{ splitChunks:{ chunks:'all' }},
/*通过s代码,让某个文件被单独打包成一个chunkimport动态导入语法:能将某个文件单独打包*/import(/* webpackChunkName :"test",webpackPrefetch:true */"./test")
webpackPrefetch
预加载,会在使用之前加载
存在兼容性问题 慎用
正常加载可以认为是并行加载 预加载为浏览器空闲时才加载
PWA
渐进式网络开发应用程序(离线访问技术),即没有网络的情况,也可以打开界面。
sw代码必须运行在服务器上
mpn i workbox-webpack-plugin -d
new WorkboxwebpackPlugin.GenerateSw({ /* 1.帮助serviceworker快速启动 2.删除旧的 serviceworker生成一个serviceworker配置文件。 */ clientsClaim:true, skipWaiting:true })
多进程打包
thread-loader
开启多进程打包。
进程启动大概为600ms,进程通信也有开销。
只有工作消耗时间比较长,才需要多进程打包,如果本身不大,使用多线程打包反而会更慢。
externals
externals:{ //拒绝jQuery被打包进来 jquery:'jQuery'}
dll
将库打包成不同的文件
配置详解
resolve
resolve:{ //配置解析模块路径别名 缺点没有提示 alias:{ $css: resolve(__dirname,"src/css") }, //配置省略文件路径的后缀名 extensions:[".js",".json"], //告诉webpack解析模块是去找哪个目录 modules:[resolve(dirname,'../../node_modules'),'node_modules']}
optimization
optimization: {splitChunks: { chunks: 'all' // 默认值,可以不写~}, // 将当前模块的记录其他模块的 hash 单独打包为一个文件 runtime // 解决:修改 a 文件导致 b 文件的 contenthash 变化runtimeChunk: { name: entrypoint => `runtime-${entrypoint.name}`},
webpack5
npm iwebpack@next webpack-cli-D
- 通过持久缓存提高构建性能.
- 使用更好的算法和默认值来改善长期缓存。
- 通过更好的树摇和代码生成来改善捆绑包大小。
- 清除处于怪异状态的内部结构,同时在v4中实现功能而不引入任何重大更改.
- 通过引入重大更改来为将来的功能做准备,以使我们能够尽可能长时间地使用v5.
添加了用于长期缓存的新算法。在生产模式下默认情况下启用这些功能。
nodejs
提供的模块需要手动添加。
webpack
内部有chunk
命名规则,不再是以id(0,1,2)命名了。
webpack
现在能够处理对嵌套模块的tree shaking
。
webpack 4
默认只能输出ES5代码
webpack5
开始新增一个属性output.ecmaVersion
,可以生成ES5
和ES6/ES2015
代码
webpack.config.js
将webpack
常用的配置放在下面,免得每次使用都需要进行配置:
const { resolve } = require("path");const MiniCssExtractPlugin = require("mini-css-extract-plugin");const OptimizeCssAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");const HtmlWebpackPlugin = require("html-webpack-plugin");const { CleanWebpackPlugin } = require("clean-webpack-plugin");
process.env.NODE_ENV = "production";const commonCssLoader = [ MiniCssExtractPlugin.loader, "css-loader", { loader: "postcss-loader", options: { ident: "postcss", plugins: () => [require("postcss-preset-env")()] } }];module.exports = { entry: ["./src/index.js", "./src/index.html"], output: { filename: "js/built.[contenthash:10].js", path: resolve(__dirname, "build"), publicPath: "/", chunkFilename: "js/[name].[contenthash:10]_chunk.js" }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, enforce: "pre", loader: "eslint-loader", options: { fix: true } }, { oneOf: [ { test: /\.css$/, use: [...commonCssLoader] },
{ test: /\.js$/, exclude: /node_modules/, loader: "babel-loader", options: { presets: [ [ "@babel/preset-env", { useBuiltIns: "usage", corejs: { version: 3 }, targets: { chrome: "60", firefox: "50", ie: "9", safari: "10", edge: "17" } } ] ], cacheDirectory: true } }, { test: /\.(jpg|png|gif)/, loader: "url-loader", options: { limit: 8 * 1024, name: "[hash:10].[ext]", outputPath: "imgs", esModule: false } }, { test: /\.html$/, loader: "html-loader" }, { exclude: /\.(js|css|less|html|jpg|png|gif)/, loader: "file-loader", options: { outputPath: "media" } } ] }] }, plugins: [ new MiniCssExtractPlugin({ filename: "css/built.[contenthash:10].css" }), new OptimizeCssAssetsWebpackPlugin(), new HtmlWebpackPlugin({ template: "./src/index.html", minify: { collapseWhitespace: true, removeComments: true } }), new CleanWebpackPlugin() ], mode: "development", devServer: { contentBase: resolve(__dirname, "build"), compress: true, port: 3000, open: true, clientLogLevel: "none", hot: true, watchContentBase: true, watchOptions: { ignored: /node_modules/ }, quiet: true }, optimization: { splitChunks: { chunks: "all" }, runtimeChunk: { name:entrypoint =>`runtime-${entrypoint.name}` } }, devtool: "source-map"};
package.json
添加
"browserslist": { "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ], "production": [ ">0.2%", "not dead", "not op_mini all" ]},"eslintConfig": { "extends": "airbnb-base", "env": { "browser": true }}
运行:
npx webpack-dev-server