模块打包工具产生的原因
ESM存在兼容问题- 模块文件过多,网络请求频繁
- 所有前端资源都需要模块化,不仅仅是
js文件 - 需要有一个工具能自动完成下列工作
- 编译最新特性,使浏览器能够兼容最新特性
- 能够将模块文件打包到一起
- 能够支持不同种类的资源类型,如图片,
html,js,css,以及其他的各种文件
常用的模块打包工具
WebpackRollupParcel
Webpack的使用
-
基本使用
- 安装
webpack及webpack-cli - 命令行运行
yarn webpack即可打包,webpack默认以/src/index.js为入口,并将打包后的js文件放在dist/main.js目录中
- 安装
-
webpack配置文件: 在项目根目录下新建webpack.config.js,该文件是运行在node中的js,遵守CommonJs规范。该文件需要导出一个对象,用来描述webpack行为。对象中的基本配置属性:-
entry: 项目的入口文件配置选项,可以是一个字符串,数组,对象- 字符串: 表示单个入口,如果是相对路径,
./不能省略 - 数组: 表示将多个文件打包到一个文件
- 对象:多文件入口,
key为chunk名,值是入口配置
- 字符串: 表示单个入口,如果是相对路径,
-
output:打包之后的输出配置,接收一个对象,对象下列基本配置属性filename: 输出文件的文件名path: 输出文件的目录,是个绝对路径,可以通过node path模块来指定绝对路径
const path = require('path') module.exports = { mode: 'none', entry: './src/index.js', output: { filename: 'bundle.js', path: path.join(__dirname, 'output'), publicPath: 'output/' }, }
-
-
工作模式,包含
3种工作模式:production、development、none,不同的模式有不同的内置优化- 指定模式的方式:
-
在命令行中指定:通过添加
--mode production/development/none命令行参数的形式添加

-
在配置对象中添加
mode属性const path = require('path') module.exports = { mode: 'none', entry: './src/index.js', output: { filename: 'bundle.js', path: path.join(__dirname, 'output'), publicPath: 'output/' }, }
-
- 工作模式的具体介绍
-
development:会将DefinePlugin中process.env.NODE_ENV的值设置为development(设置NODE_ENV时不会设置mode). 为模块和chunk启用有效的名。

-
production(默认):会将DefinePlugin中process.env.NODE_ENV的值设置为production。为模块和chunk启用确定性的混淆名称,FlagDependencyUsagePlugin,FlagIncludedChunksPlugin,ModuleConcatenationPlugin,NoEmitOnErrorsPlugin和TerserPlugin。

-
none:不使用任何默认优化选项

-
- 指定模式的方式:
-
webpack资源模块加载-
webpack中默认只会处理js文件,其他资源模块一般都需要经过loader处理,然后在js中引入。loader是webpack的一个核心功能。 这样设计的目的:- 根据代码加载资源,按需加载。
JavaScript驱动前端应用 - 确保上线的文件不会缺失,都是必要的
- 根据代码加载资源,按需加载。
-
css资源的加载:需要先经过css-loader处理,将css代码转换为一个js模块,然后再经过style-loader将css样式通过style标签的方式引入。- 先安装
css-loader和style-loader - 在
webpack.config.js中配置module->rules。rules接收一个对象数组-
对象中的test为文件匹配的正则
-
use: 表示使用的
loader,可以是一个字符串和数组,如果是一个数组,文件则会从后向前依次经过多个loader转换。const path = require('path') module.exports = { mode: 'none', entry: './src/index.js', output: { filename: 'bundle.js', path: path.join(__dirname, 'output'), publicPath: 'output/' }, module: { rules: [ { test: /.css$/, use: [ 'style-loader', 'css-loader' ] } ] } }
-
- 先安装
-
文件资源的加载:
file-loader,file-loader可以将文件资源转化为js模块,通过import导入之后可以拿到文件资源对应的路径。// webpack.config.js const path = require('path') module.exports = { mode: 'none', entry: './src/index.js', output: { filename: 'bundle.js', path: path.join(__dirname, 'output'), publicPath: 'output/' }, module: { rules: [ { test: /.css$/, use: [ 'style-loader', 'css-loader' ] } ] } } // 使用 import bg from './loginBg.jpg' const img = new Image() img.src = bg此外还可以使用
url-loader来转换文件。会将文件转换为DataURL的形式。但是只适合体积较小的文件, 体积较大的文件打包之后会导致打包之后的文件过大。因此需要限制不超过指定大小的文件使用url-loader处理(通过url-loader配置选项中的limit来配置),对于超出限制大小的文件默认会使用file-loader进行处理,所以需要默认安装file-loader。url-loader转换后的文件不会有实体文件,而是一串DataURL字符串。
DataURL格式:

DataURL字符串示例

url-loader使用配置示例const path = require('path') module.exports = { mode: 'none', entry: './src/index.js', output: { filename: 'bundle.js', path: path.join(__dirname, 'output'), publicPath: 'output/' }, module: { rules: [ { test: /.(png|jpg)$/, use: { loader: 'url-loader', options: { limit: 10 * 1024 } }, } ] } } -
转换
ES6代码到ES5。通过babel-loader来实现js代码的编译转换webpack只负责打包,代码的编译通过loader来实现-
安装
babel-loader@babel/core@babel/preset-env -
在
webpack.config.js中添加配置const path = require('path') module.exports = { mode: 'none', entry: './src/index.js', output: { filename: 'bundle.js', path: path.join(__dirname, 'output'), publicPath: 'output/' }, module: { rules: [ { test: /.js$/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } } } ] } } -
重新运行打包,可以查看打包后的对应文件的
js代码已经转换为es5。
-
-
常见加载器分类,即
loader分类- 文件类:主要用于加载文件资源,并返回相应的内容。如
url-loader,file-loader等 - 语法转换类:用于将资源转换为指定语法的代码,如
babel-loader、ts-loader等 - 样式类:用于加载转换各种样式资源,如
css-loader,style-loader,less-loader,sass-loader等 - 语法检查和测试类:用于检查语法或测试,如
eslint-loader
- 文件类:主要用于加载文件资源,并返回相应的内容。如
-
-
webpack加载资源的方式js加载 ,支持ESM、CommonJs、AMD等模块化加载方式loader加载的非js文件,如css中的@import和url函数 ,html-loaderimg、video等标签 的src属性。
-
webpack核心工作原理- 根据配置文件确定入口文件
- 通过解析入口文件中的
import/require等解析文件的依赖文件, 逐步深入查找并构建表示项目文件的依赖关系的依赖树 - 对依赖树进行递归,找到对应的文件,并根据配置文件中的
module下的rules规则对文件进行加载 - 将加载后的文件合并输出到
bundle.js中
-
编写
loader-
loader是导出为一个函数的node模块,该函数在loader转换资源的时候调用-
接收上一个
loader产生的结果或者资源文件(resource file)作为参数 -
函数的 this 上下文由 webpack 填充
-
函数可以返回一个代表
JavaScript源码的String / Buffer,注意为防止源码中的特殊字符出现识别错误,需要将源码字符串进行转义const marked = require('marked') module.exports = source => { const html = marked(source) return `export default ${JSON.stringify(html)}` } -
也可以直接返回一个字符串,交给下一个
loader处理,如下图直接返回html字符串,然后使用html-loader进行处理const marked = require('marked') module.exports = source => { const html = marked(source) return html }
-
-
loader特点:loader实际上是一个输入到输出的转换loader支持链式调用,一个文件资源可以依次经过多个loader处理
-
-
webpack插件:增强webpack自动化能力-
用途:处理资源加载之外的自动化工作
- 打包之前清除目录
- 拷贝静态文件到输出目录
- 压缩输出代码
-
常见插件的使用
-
自动清除输出目录
clean-webpack-plugin,cleanStaleWebpackAssets自动构建时是否清除未使用的资源。const { CleanWebpackPlugin } = require('clean-webpack-plugin') module.exports = { mode: 'none', entry: './src/index.js', output: { filename: 'bundle.js', path: path.join(__dirname, 'output'), // publicPath: 'output/' }, plugins: [ new CleanWebpackPlugin({ cleanStaleWebpackAssets: false }) ] } -
自动生成
HTML插件html-webpack-plugin-
基本使用
const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { mode: 'none', entry: './src/index.js', output: { filename: 'bundle.js', path: path.join(__dirname, 'output'), // publicPath: 'output/' }, plugins: [ new CleanWebpackPlugin({ cleanStaleWebpackAssets: false }), new HtmlWebpackPlugin(), ] } -
修改标题,添加源数据标签
new HtmlWebpackPlugin({ title: 'webpack-loader', meta: { viewport: 'widt=device-width' }, }),
-
指定模板,在使用插件是指定配置属性
template,在html模板文件中可以使用ejs模板语法,通过htmlWebpackPlugin.options可以访问到插件的配置属性。new HtmlWebpackPlugin({ title: 'webpack-loader', meta: { viewport: 'widt=device-width' }, template: './index.html' }),<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title><%= htmlWebpackPlugin.options.title %></title> </head> <body> <h1> <%= htmlWebpackPlugin.options.title %> </h1> </body> </html> -
创建多个
HTML文件,只需要在plugins中添加多个实例。通过filename配置来指定生成的文件名称。plugins: [ new HtmlWebpackPlugin({ title: 'webpack-loader', meta: { viewport: 'widt=device-width' }, template: './index.html' }), new HtmlWebpackPlugin({ filename: 'mutiple.html' }), ]
-
-
复制文件到输出目录
copy-webpack-plugin,patterns是要复制的文件目录列表。new CopyWebpackPlguin([ 'public' ]),
-
-
插件的编写
-
webpack通过在每个环节挂载钩子函数的方式来实现插件的扩展

-
webpack插件是一个函数或类,函数或类的prototype中必须包含一个apply方法 -
然后根据插件的需求,确定插件所在事件钩子,并在该钩子中通过
tap绑定一个回调函数到事件钩子中,在构建到该钩子所处阶段时会调用该回调函数。 -
然后再函数中进行相应的处理
compiler: 代表了完整的webpack环境配置,包括options、loader、plugin、hooks等compilation:代表当前的构建的上下文环境,包括当前的模块资源,编译生成资源、变化的文件、以及被跟踪依赖的状态信息。通过compilation.assets对象访问到所有编译后的内容。
class MyPlugin { // 在 prototype 上定义一个接收 compiler 对象的 apply 方法 apply(compiler) { // 指定要附加到的钩子事件 compiler.hooks.emit.tap( 'MyPlugin', (compilation) => { const assets = compilation.assets; for(const f in assets) { if (f.endsWith('.js')) { const newContent = assets[f].source().replace(/\/\**\*\//g, '') assets[f] = { source: () => newContent, size: () => newContent.length } } } } ); } }
-
-
-
开发体验增强
-
文件改变之后自动打包:以 监视模式 运行
webpack打包,监听文件变化之后会自动运行webpack打包。命令行添加--watch参数将开启监视模式。 -
webpack-dev-serve:webpack本地开发功能插件,打包项目,并启动本地http server,监听文件变化,自动重新打包,并刷新浏览器。-
安装
webpack-dev-server之后, 直接运行yarn webpack-dev-sever,会自动以监视模式打包,并启动本地http server,文件修改自动打包,并刷新浏览器。 -
指定静态资源目录:通过
webpack.config.js中配置devServer下的contentBase,会从指定的目录下获取静态资源module.exports = { mode: 'none', entry: './src/index.js', output: { filename: 'bundle.js', path: path.join(__dirname, 'output'), // publicPath: 'output/' }, devServer: { contentBase: 'public', } } -
配置代理
通过webpack.config.js中配置devServer下的proxy来配置module.exports = { mode: 'none', entry: './src/index.js', output: { filename: 'bundle.js', path: path.join(__dirname, 'output'), // publicPath: 'output/' }, devServer: { contentBase: 'public', proxy: { '/api': { // 以 /api 开头的 localhost:8080 请求将被 替换为 https://api.guthub.com 即localhost:8080/api/user -> https://api.guthub.com/api/user target: 'https://api.github.com', // 重写替换后的请求路径,将 './api' 部分替换为 ‘’ pathRewrite: { '/api': '' }, // 不能使用 localhost:8080 作为请求 GitHub 的主机名 changeOrigin: true, } } } }
-
-
源代码映射
(Source Map)-
Source Map: 开发阶段帮助直接定位到源码。关键文件为一个.map文件,文件保存了压缩代码文件到源文件的映射关系。文件为一个JSON格式的文件,包括以下属性:-
version:文件版本 -
sources: 源文件名,可能是多个文件合并产生的,所以是一个数组 -
names:源码中定义的变量名

-
mappings:记录转换之后以及转换之前代码的映射关系,是一个base64-vlq编码的字符串

-
如何在压缩代码中引用
source Map文件:在压缩代码最后一行添加注释引入。引入之后将会自动请求源代码,所以map文件所在目录下需要有源码文件,否则将无法查看源码

-
-
webpack开启Source Map: 通过在webpack.config.js配置中添加devtool属性,并指定一个表示Source Map模式的值,下列是所有模式的比较。

从模式命名解读各模式的区别:打包时不生成source map文件,打包速度快,但是错误定位时只能定位到文件。-
eval,使用eval函数执行打包后的代码,只能定位文件

-
cheap,简易版,只能定位行,不能定位列 -
source-map,生成source-map文件 -
module,不经过loader转换 -
inline,以dataURL的形式将source-map代码嵌入到生成的文件中 -
hidden,浏览器中隐藏source-map文件,但是会生成source Map文件 -
nosources,不在浏览器中显示source-map文件,会提示行列信息
-
-
-
自动刷新问题:自动刷新时会自动刷新浏览器,导致页面状态丢失,解决办法就是开启
HMR-
开启
HMR有两种方式:-
启动
webpack-dev-server时添加命令行参数--hot

-
配置
webpack.config.js,在devServer中添加hot:true,并在plugins中添加new webpack.HotModuleReplacementPlugin()module.exports = { mode: 'none', entry: './src/index.js', output: { filename: 'bundle.js', path: path.join(__dirname, 'output'), // publicPath: 'output/' }, devServer: { hot: true, contentBase: 'public', proxy: { '/api': { // 以 /api 开头的 localhost:8080 请求将被 替换为 https://api.guthub.com 即localhost:8080/api/user -> https://api.guthub.com/api/user target: 'https://api.github.com', // 重写替换后的请求路径,将 './api' 部分替换为 ‘’ pathRewrite: { '/api': '' }, // 不能使用 localhost:8080 作为请求 GitHub 的主机名 changeOrigin: true, } } } plugins: [ new HtmlWebpackPlugin({ title: 'webpack-loader', meta: { viewport: 'widt=device-width' }, template: './index.html' }), new webpack.HotModuleReplacementPlugin(), ] }
-
-
HMR的问题-
需要手动处理当模块更新之后如何将更新之后的代码替换到页面中
-
样式文件在开启
HMR之后,文件更新能直接替换到页面中,而不改变页面状态,是因为style-loader中自动处理了样式的热更新
开启source Map后能在开发者工具中的sources下的webpack->src中找到对应处理的js

-
样式文件能够统一处理热更新是因为只需要将修改的样式替换即可,而
js代码则是由于代码逻辑各不相同,无法统一替换执行 -
某些框架也提供了脚手架也集成了
HMR方案,所以可以直接使用。因为框架的js代码相对而言更有规律,或是导出一个函数或是导出一个对象,所以能够实现统一的逻辑来处理文件的热更新。 -
手动处理
js文件的热更新,通过使用处理HMR的API-
在引用模块的文件中,通过
module.hot.accept(url, func)注册模块热更新处理 -
module.hot.accept()接收两个参数,第一个参数为模块的路径,第二个参数为模块热更新的逻辑。// heading.js import './heading.css' export default () => { const ele = document.createElement('div') ele.contentEditable = true ele.className = 'head' return ele } // index.js import createHello from './heading' import test from './test.md' import bg from './images/loginBg.jpg' const helloEle = createHello() document.body.appendChild(helloEle) document.body.style.background = `url(${bg})` let lastEel = helloEle // 根据代码逻辑,heading 文件是导出一个生成元素的函数,文件更新时只要重新生成元素替换掉原来的元素即可, module.hot.accept('./heading', () => { // 根据操作,先生成一个新的元素,并记录 const newEle = createHello() // 移除原来的元素 // document.body.removeChild(lastEel) // 将原来元素的状态保存到新的元素中 newEle.innerHTML = lastEel.innerHTML // // 替换原来的元素 document.body.replaceChild(newEle, lastEel) lastEel = newEle }) ```
-
-
处理图片的热更新
图片的热更新只需要将引用图片的位置重新替换图片路径即可。import createHello from './heading' import test from './test.md' import bg from './images/loginBg.jpg' const helloEle = createHello() document.body.appendChild(helloEle) document.body.style.background = `url(${bg})` // 图片热更新处理 module.hot.accept('./images/loginBg.jpg', () => { document.body.style.background = `url(${bg})` }) -
HMR注意事项-
module.hot是HMR的插件提供的,若是没有开启HMR时,module.hot不存在,热更新的处理逻辑将报错,所以需要将热更新的处理逻辑包裹在module.hot是否存在的判断中if(module.hot) { let lastEel = helloEle // 根据代码逻辑,heading 文件是导出一个生成元素的函数,文件更新时只要重新生成元素替换掉原来的元素即可, module.hot.accept('./heading', () => { // 根据操作,先生成一个新的元素,并记录 const newEle = createHello() // 移除原来的元素 // document.body.removeChild(lastEel) // 将原来元素的状态保存到新的元素中 newEle2.innerHTML = lastEel.innerHTML // // 替换原来的元素 document.body.replaceChild(newEle, lastEel) lastEel = newEle }) // 图片热更新处理 module.hot.accept('./images/loginBg.jpg', () => { document.body.style.background = `url(${bg})` }) } -
打包时需要关闭
HMR,去除HMR相关的插件,这样在运行webpack命令打包时,热更新的代码会被包裹在一个if(false){}的逻辑判断中,压缩代码时也会将这种无用的逻辑判断代码去除。

-
-
-
-
-
webapck不同环境下的配置-
根据环境导出不同的配置:
webapck.config.js还支持导出一个函数,函数接收两个参数,第一个参数为--env命令行参数的值,第二个参数为运行命令行的所有参数对象。module.exports = (env, argv) => { console.log(env, argv); }
module.exports = (env, argv) => { const config = { mode: 'development', entry: './src/index.js', output: { filename: 'bundle.js', path: path.join(__dirname, 'output'), // publicPath: 'output/' }, devtool: 'cheap-module-source-map', module: { rules: [ { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] }, { test: /\.jpg/, use: 'file-loader' } ] }, plugins: [ new HtmlWebpackPlugin({ title: 'webpack-loader', meta: { viewport: 'widt=device-width' }, template: './index.html' }), ] } if (env === 'production') { // 生产环境 config.mode = env config.devtool = false config.plugins = [ ...config.plugins, new CleanWebpackPlugin(), new CopyWebpackPlguin(['public']) ] } else { config.devServer = { hot: true, // hotOnly: true, contentBase: 'public', proxy: { '/api': { // 以 /api 开头的 localhost:8080 请求将被 替换为 https://api.guthub.com 即localhost:8080/api/user -> https://api.guthub.com/api/user target: 'https://api.github.com', // 重写替换后的请求路径,将 './api' 部分替换为 ‘’ pathRewrite: { '/api': '' }, // 不能使用 localhost:8080 作为请求 GitHub 的主机名 changeOrigin: true, } } } config.plugins = [ ...config.plugins, new webpack.HotModuleReplacementPlugin(), ] } return config } -
不同环境对应不同的配置文件
-
首先,新建
webpack.common.js、webpack.dev.js、webpack.prod.js分别存在公共配置、开发环境配置以及生产环境配置 -
分别将配置写入到三个配置文件中
-
引入
webpack-merge,然后分别在webpack.dev.js和webpack.prod.js中使用webpack-merge提供的merge方法合并webpack.common.js中的配置。// webpack.common.js const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { mode: 'development', entry: './src/index.js', output: { filename: 'bundle.js', path: path.join(__dirname, '../output'), // publicPath: 'output/' }, devtool: 'cheap-module-source-map', module: { rules: [ { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] }, { test: /\.jpg/, use: 'file-loader' } ] }, plugins: [ new HtmlWebpackPlugin({ title: 'webpack-loader', meta: { viewport: 'widt=device-width' }, template: './index.html' }), ] } //webpack.dev.js const { merge } = require('webpack-merge') const common = require('./webpack.common') const webpack = require('webpack') module.exports = merge(common, { devServer: { hot: true, // hotOnly: true, contentBase: 'public', proxy: { '/api': { // 以 /api 开头的 localhost:8080 请求将被 替换为 https://api.guthub.com 即localhost:8080/api/user -> https://api.guthub.com/api/user target: 'https://api.github.com', // 重写替换后的请求路径,将 './api' 部分替换为 ‘’ pathRewrite: { '/api': '' }, // 不能使用 localhost:8080 作为请求 GitHub 的主机名 changeOrigin: true, } } }, plugins: [ new webpack.HotModuleReplacementPlugin(), ] }) //webpack.prod.js const { merge } = require('webpack-merge') const common = require('./webpack.common') const { CleanWebpackPlugin } = require('clean-webpack-plugin') const CopyWebpackPlguin = require('copy-webpack-plugin') module.exports = merge(common, { mode: 'production', devtool: false, plugins: [ new CleanWebpackPlugin(), new CopyWebpackPlguin(['public']) ] }) -
运行命令行时需要通过
--config命令行参数来指定配置文件

-
-
-
Webpack DefinePlugin:为代码注入全局成员plugins: [ new webpack.DefinePlugin({ TEST: 'test' }) ]-
production模式下,会默认启动此插件并注入一个process.env.NODE_ENV=production的成员 -
传入插件的对象中的键值对中的值是一个
js代码片段的字符串
在js中使用注入的全局变量
打包之后的代码,直接将值输出到代码中

修改全局变量的值如下

在
js中使用

打包之后的代码如下

-
如果想要传入一个字符串,可以通过
JSON.stringify()将字符串转换为一个js代码片段


-
-
Tree Shaking-
Tree Shakingproduction模式下,会默认启用Tree Shaking,移除未引用代码(dead-code) -
在非
production模式下启用Tree Shakingmodule.exports = { entry: './src/index.js', mode: 'none', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') }, optimization: { // 配置 webpack 内置优化功能 usedExports: true, // 只导出外部使用的成员(标记未使用代码) concatenateModules: true, // 将所有模块合并到一个函数中, Scope Hoisting ,作用域提升 minimize: true, // 开启代码压缩(移除未使用代码) } } -
如果使用了
babel-loader,Tree Shaking将失效-
Tree Shaking工作的前提条件是代码使用ESM规范 -
babel-loader早期版本是默认将代码转换成CommonJS规范。最新版本是支持ESM的,如果想要指定编译之后的模块化规范,可以通过@babel/preset-env的配置选项的modules来指定。const path = require('path') module.exports = { entry: './src/index.js', mode: 'none', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') }, optimization: { // 配置 webpack 内置优化功能 usedExports: true, // 只导出外部使用的成员(标记未使用代码) concatenateModules: true, // 将所有模块合并到一个函数中, Scope Hoisting ,作用域提升 minimize: true, // 开启代码压缩(移除未使用代码) }, module: { rules: [ { test: /\.js$/, use: [ { loader: 'babel-loader', options: { presets: [ ['@babel/preset-env', { modules: 'commonjs'}] ] } } ] } ] } }按照上述配置打包之后的代码如下包含了未引用代码,说明没有启用
Tree Shaking

去掉@babel/preset-env的module配置之后打包生成的代码如下:移除了未引用代码,启用了Tree Shaking

-
-
-
副作用
SideEffects- 副作用是模块除了导出成员之外所做的事情,比如在原型中添加方法或属性等,
css文件都属于副作用模块。 SideEffects功能的作用是移除项目中没有被用到且没有副作用的代码。- 开启
SideEffects- 首先需要在
webpack.config.js中配置optimization中的SideEffect为true,表示开启SideEffect功能。 - 然后在
package.json中配置sideEffects,可以配置为一个boolean或文件路径一个字符串数组。-
boolean:表示项目中的所有代码都有/没有副作用, -
数组:表示项目中指定的文件具有副作用,打包时不会移除这些文件
"sideEffects": false, "sideEffects": [ "*.css" ]
-
- 首先需要在
- 副作用是模块除了导出成员之外所做的事情,比如在原型中添加方法或属性等,
-
代码分割
Code Splitting
实现代码分割有两种方式:-
多入口打包:适用于多页面应用,一个页面对应一个大包入口,公共部分则单独提取
-
需要将
entry配置为一个对象,指定多个入口,同时利用HtmlWebpackPlugin生成多个html文件const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { entry: { index: './src/index.js', album: './src/album.js' }, mode: 'none', output: { filename: '[name].bundle.js', path: path.resolve(__dirname, 'dist') }, module: { rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader'] } ] }, plugins: [ new HtmlWebpackPlugin({ filename: 'index.html', template: './src/index.html' }), new HtmlWebpackPlugin({ filename: 'album.html', template: './src/album.html' }) ] } -
HtmlWebpackPlugin默认会将打包生成的所有bundle.js引入到生成的html文件中,这就意味着除了引入需要的bundle.js,还会引入其他入口生成的bundle.js

-
通过在
HtmlWebpackPlugin插件中指定chunks,来指定生成的html需要引入哪些bundle.js。plugins: [ new HtmlWebpackPlugin({ filename: 'index.html', template: './src/index.html', chunks: ['index'] }), new HtmlWebpackPlugin({ filename: 'album.html', template: './src/album.html', chunks: ['album'] }) ]
-
提取公共模块:提取多个入口的公共部分到一个单独的
bundle.js中。
提取公共模块功能的开启只需要在optimization属性中配置splitChunks,然后在splitChunks中通过chunks指定提取哪些公共模块到一个bundle.js中。optimization: { splitChunks: { chunks: 'all', // all 表示将所有的公共模块提取到单独的bundle.js中,‘async’:按需加载的模块 } },但是最新的
css-loader需要将手动启用css模块化,才能成功提取公共模块。module: { rules: [ { test: /\.css$/, use: [ 'style-loader', { loader: 'css-loader', options: { modules: true } } ] } ] },
-
-
模块的动态导入
- 模块的动态导入只需要将原来代码中使用
import关键字导入的模块改成使用import()函数导入,无需进行配置,webpack会自动处理代码分割。 webpack默认以chunkId作为chunk名,如果想设置chunk名,可以import()方法参数使用行内注释webpackChunkName来指定import(/* webpackChunkName: 'components' */'./posts/posts').then(({ default: posts }) => { mainElement.appendChild(posts()) })
不指定chunk名时的打包结果

- 模块的动态导入只需要将原来代码中使用
-
css文件提取,将css代码提取到一个css文件中,适合于css代码体积较大的场景.使用MiniCssExtractPlugin插件。
在plugins中添加插件实例,将module-->rules中的style-loader修改为MiniCssExtractPlugin.loader,因为css代码不再是通过<style>标签注入,而是通过<link>标签引入。const { CleanWebpackPlugin } = require('clean-webpack-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin') const MiniCssExtractPlugin = require('mini-css-extract-plugin') module.exports = { mode: 'none', entry: { main: './src/index.js' }, output: { filename: '[name].bundle.js' }, module: { rules: [ { test: /\.css$/, use: [ // 'style-loader', MiniCssExtractPlugin.loader, 'css-loader' ] } ] }, plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ title: 'Dynamic import', template: './src/index.html', filename: 'index.html' }), new MiniCssExtractPlugin(), ] }生产模式下提取的
css文件不会自动压缩,需要使用OptimizeCssAssetsWebpackPlugin插件来帮助压缩css文件。
-
-
文件名
Hash-
客户端会缓存服务器的文件,为尽可能的多的利用客户端的缓存文件,减少文件的请求次数,同时又能及时更新有变化的文件,我们需要在文件变化时改变文件名,让客户端重新请求变化之后的文件,同时对于没有发生改变的文件,不改变文件名,从而让客户端利用缓存中的文件。
-
webpack支持三种形式的文件名Hashhash:项目级别的Hash,只要文件有修改,重新打包之后会重新生成Hashchunkhash:chunk级别的Hash,文件修改,只会改变对应chunk的Hashcontenthash:内容级别的Hash,文件修改,只会改变内容对应文件的Hash
-
指定
Hash长度。通过在hash后面跟上:num来指定生成Hash长度。output: { filename: '[name]-[contenthash:6].bundle.js' },
-
Rollup的使用
-
Rollup是一款ESM打包器,充分利用ESM特性的高效打包器 -
Rollup的简单使用
安装之后,使用yarn rollup entryPath命令行来运行rollup打包,然后可以通过--file来指定输出文件,--format指定输出的代码格式。rollup默认会启动Tree Shaking优化打包之后的结果,移除未引用代码。

-
Rollup配置文件- 配置文件
rollup.config.js是一个node模块,但是rollup会对配置文件进行处理,因此可以直接使用ESM。rollup.config.js需要默认导出一个对象。export default { input: './src/index.js', // 入口文件 output: { // 输出文件配置 file: 'dist/main.js', // 输出文件位置 format: 'iife', // 输出文件格式 } } Rollup默认不会使用配置文件,需要通过--config [file]指定配置文件,如果不指定file将默认使用根目录下的rollup.config.js文件

- 配置文件
-
Rollup使用插件Rollup自身功能只是ESM模块的合并打包,其他功能需要通过插件去扩展,插件是Rollup唯一的扩展途径。- 使用插件,以
rollup-plugin-json(让我们可以导入json文件)为例。安装插件之后,直接在配置文件中引入,并在plugins中配置import json from 'rollup-plugin-json' export default { input: './src/index.js', // 入口文件 output: { // 输出文件配置 file: 'dist/main.js', // 输出文件位置 format: 'iife', // 输出文件格式 }, plugins: [ json() ] } Rollup默认只能按照文件路径的方式加载本地模块,对于node_modules中的模块并不能和webpack一样直接通过名称导入。为了能和webpack一样直接通过模块名导入第三方模块,需要使用rollup-plugin-node-resolve插件,使用方法同上。注意rollup默认只能处理ESM模块,所以如果不添加特殊处理,第三方模块也必须是ESM模块。- 加载
CommonJs模块,需要添加rollup-plugin-commonjs插件,使用方法同上。
-
Rollup代码拆分- 使用
import()函数动态导入,Rollup会自动拆分代码。但是输出代码的格式不能时iife,因为自执行函数不支持代码拆分,所有代码包裹在一个函数中,无法进行代码拆分。浏览器环境我们只能将代码格式指定为amd。同时代码拆分会生成多个文件,所以输出文件不能通过file指定,file只能指定单个文件。需要通过dir指定一个输出目录import json from 'rollup-plugin-json' export default { input: './src/index.js', // 入口文件 output: { // 输出文件配置 dir: 'dist', format: 'amd' }, plugins: [ json() ] }
- 使用
-
多入口代码
多入口打包只需要将input修改为一个数组或对象即可。rollup在多入口打包时会自动使用代码拆分,所以format不能是iife,需要指定为amd,但是amd格式的js文件不能直接引入到页面,需要通过实现AMD标准的库(如require)加载。加载时使用<script>标签引入require.js,并通过data-main指定requirejs加载模块的入口路径。import json from 'rollup-plugin-json' export default { input: ['./src/index.js', './src/message.js'], // 入口文件 output: { // 输出文件配置 dir: 'dist', format: 'amd' }, plugins: [ json() ] }
-
优缺点:
- 优点:
- 输出结果更加偏平
- 自动移除未引用代码
- 打包结果直接可读
- 缺点:
- 加载非
ESM的第三方模块比较复杂 - 模块最终都被打包到一个函数中,无法实现
HMR - 浏览器环境中,代码拆分依赖
AMD库,需要依赖AMD库加载amd格式的js
- 加载非
- 优点:
Parcel的使用
Parcel是一款零配置的前端应用打包工具。
-
parcel的使用-
安装
parcel-bundler插件, 运行命令yarn parcel entryFile即可。

-
parcel推荐使用.html文件为入口文件,因为浏览器也是使用.html文件作为入口。通过script标签引入入口的js,然后根据模块导入来构建依赖。<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>parcel study</title> </head> <body> <script src="./main.js"></script> </body> </html> -
运行
parcel命令之后会自动构建打包,并启动一个本地http server,文件修改时会自动重新构建代码并刷新浏览器。 -
parcel也支持手动处理热更新import foo from './foo' // import $ from 'jquery' import './main.css' import logo from './logo.png' foo.bar() import('jquery').then($ => { $(document.body).append('<h1>Hello Parcel</h1>') $(document.body).append(`<img src=${logo} alt="" />`) }) if(module.hot) { module.hot.accept(() => { console.log('hmr'); }) }
-
-
parcel的特点- 完全零配置
- 自动处理各种类型的文件资源的加载
- 引入第三方模块时会自动安装依赖,无需手动安装
- 相同体量,打包速度比
webpack更快,因为内部使用多线程进行打包。虽然webpack可以使用happypack插件来加速打包。
本文详细介绍了前端模块打包工具的重要性和作用,重点讲解了Webpack的使用,包括基本配置、工作模式、资源模块加载、常见加载器和插件的运用。还提到了其他打包工具如Rollup和Parcel的特点和使用,探讨了不同工具在代码分割、优化和自动化工作流程中的策略,为前端开发提供打包解决方案。
380

被折叠的 条评论
为什么被折叠?



