温州高端企业网站建设,squarespace wordpress,北京自助模板建站,服务网站设计案例什么是webpack
webpack是什么#xff0c;官网中是这么说的。 本质上#xff0c;webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时#xff0c;它会递归地构建一个依赖关系图(dependency graph)#xff0c;其中包…什么是webpack
webpack是什么官网中是这么说的。 本质上webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时它会递归地构建一个依赖关系图(dependency graph)其中包含应用程序需要的每个模块然后将所有这些模块打包成一个或多个 bundle。 从上面的概念中我们可以看出webpack是一个会把应用程序中具有依赖关系的每个模块打包成一个或者多个bundle的工具。既然是工具就是代替人工完成某些复杂的操作减少开发工作的繁琐所以我们需要掌握的就是如何使用它。
但我们在使用之前需要明白文中的几个概念 什么是模块了解Node的同学都知道在Node的世界里万物皆模块那当然基于Node的产物webpack对模块的定义和node是类似的模块可大可小大到一个项目一个工程小至一个文件甚至一行代码。 什么是budle和模块相比budle的定义则显得比较局限它是指打包后的资源文件一个文件就是一个budle一个前端工程可以打包成一个文件或者多个文件的文件夹这就是概念中提到的“一个或多个 bundle”。 此外除了上述的两个概念之外这里还需要在补充几个相关概念。 hashhash是和项目相关的hash值一个项目一个hash值是webpack用来给标记budle文件的唯一标识也是监测该资源文件在二次打包时是不是要覆盖的依据通常用固定字节的字符串来表示。 chunk/chunkhashchunk是指代码块也可以泛指为1个chunk1budle。 我们知道一个打包资源中会有多个budle文件如果我们修改其中一个那和项目相关的hash值就会统一改变而那些没有修改过的、已打包好的、并且已经缓存的budle文件也会因此失效需要重新打包为了解决这个问题就出现了chunkhash的概念一个budle文件就对应一个chunkhash。 contenthash和chunkhash相比contenthash的粒度就更细了它主要是针对一个bundle中的内容如果内容不变contenthash就不变所以我们通常会把项目中的js和css分离开来对于css的打包文件命名我们就采用contenthash。
为什么要用webpack 前后端分离开发模式是催化剂 随着开发模式的转变现在的前端开发已经不是想以往那些php、jsp那些但凡懂点html/css的后端程序员都可以上手做的现在的前端已经开始专业化而我们的代码也是以工程的形式存在你可以使用任何框架和技术但是最后跑在用户浏览器上的东西还是和以前一样就是静态资源和html而从框架代码到浏览器识别的资源之间的转化就是由webpack来完成的。 工程化、模块化的产物 工程化、模块化是我们研发代码的一种重要思想同时也正是这种思想的存在才会在前端领域中衍生出那么多框架和技术比如Vue、React、Angular等工程化框架TS、less、Scss等各种语言的扩展这些都是提高我们开发效率的利器但是同时这些语言和框架是不能被浏览器所识别的怎样让我们的代码更优雅的被浏览器识别并运行这就是webpack的主要职责。
webpack的安装
环境准备
webpack是基于nodek开发的工程化工具所以开发环境支持node并且node版本和webpack的匹配也很重要具体参见webpack官网的发布说明尽量使用最新版本以提高打包速度。
全局安装 不推荐
# 安装webpack V4版本时需要额外安装webpack-cli
npm install webpack webpack-cli -g
# 检查版本
webpack -v
# 卸载
npm uninstall webpack webpack-cli -g项目安装 推荐
# 安装最新的稳定版本
npm i -D webpack
# 安装指定版本
npm i -D webpackversion
# 安装最新的体验版本 可能包含bug,不不要⽤用于⽣生产环境
npm i -D webpackbeta
# 安装webpack V4版本时需要额外安装webpack-cli
npm i -D webpack-cliwebpack的构建
默认配置
首先我们先看一下webpack的默认配置代码如下
const path require(path)
module.exports {// 必填webpack的执行入口entry: ./src/main.js,output: {// 合并输出的文件名filename: main.js,// 合并输出的绝对存放地址path: path.resolve(__dirname, ./dist)}
} 其中我们需要注意的就是
webpack默认只支持JS模块和JSON模块支持CommonJS Es moudule AMD等模块类型webpack4⽀持零配置使⽤但是可用性很差一般都需要额外扩展配置
执行构建
可以通过这两种方式进行项目构建
# 方式1npx方式
npx webpack# 方式2npm script
# 首先需要在package.json中添加test指令对应的执行脚本
----------------------------------------------scripts:{ test: webpack // 原理就是通过shell脚本在node_modules/.bin⽬录下创建⼀个软链接},
----------------------------------------------
# 然后在执行如下脚本
npm run test 构建成功后会发现⽬录下多出⼀个 dist ⽬录⾥⾯有个 main.js 这个⽂件是⼀个可执行的JavaScript文件里面包含webpackBootstrap启动函数。
SourceMap
源代码与打包后的代码的映射关系通过sourceMap定位到源代码。
在dev模式中默认开启关闭的话可以在配置文件⾥ devtool:none
// devtool的介绍 https://webpack.js.org/configuration/devtool#devtool
eval: 速度最快,使用eval包裹模块代码,
source-map: 产生.map文件
cheap: 较快不包含列信息
Module: 第三⽅模块包含loader的sourcemap(比如jsx to js babel的sourcemap)
inline: 将.map作为DataURI嵌入不单独生成.map⽂件配置推荐:
devtool:cheap-module-eval-source-map,// 开发环境配置// 线上不推荐开启
devtool:cheap-module-source-map, // 线上生成配置WebpackDevServer
webpack-dev-server提升开发效率的利器可以解决重复打包生成dist目录这是因为devServer把打包后的模块不会放在dist目录下二是放到内存中从而提升速度。
# 安装
npm install webpack-dev-server -D# 修改package.json配置
scripts: {server: webpack-dev-server
},# 修改webpack.config.js配置
devServer: {contentBase: ./dist, open: true,port: 8081
}# 启动
npm run server跨域问题的解决
设置服务器代理
devServer: {......proxy: {/api: {target: http://localhost:9092}}
}本地mock server
devServer: {......// before()是devServer的钩子函数除了before之外还有after本质上只有执行时间顺序的问题// app是一个本地的node服务可以是express也可以是koa这里是expressbefore(app, server) {app.get(/api/mock.json, (req, res) {res.json({hello: world})})}
}Hot Module Replacement (HMR:热模块替换) 启动hmr devServer: {contentBase: ./dist,open: true,hot: true, // 开启HMRhotOnly: true // 即便HMR不生效浏览器也不自动刷新就开启hotOnly
},配置webpack.config.js const webpack require(webpack);// 在插件配置处添加
plugins: [new CleanWebpackPlugin(),new webpack.HotModuleReplacementPlugin()
]注意启动HMR后css抽离会不生效还有不支持contenthashchunkhash 建议使用style-loader将css处理到html中 处理js模块HMR需要使用module.hot.accept来观察模块更新从⽽更新 if (module.hot) {// path为需要监听变化的js的相对路径module.hot.accept(path, function() {// 执行操作重新渲染修改后的js效果});
}webpack的基本概念
entry
指定webpack打包⼊口文件webpack 执⾏构建的第一步将从Entry开始可理解成输入有三种不同的输入方式
// 字符串: 单入口
entry: ./src/index.js,// 数组: 单入口最终将数组文件合并成一个
entry: [./src/index.js, ./src/other.js],// 对象: 多入口
entry: { index: ./src/index.js,other: ./src/other.js
}output
打包转换后的⽂件输出到磁盘位置输出结果在webpack经过⼀系列处理并得出最终想要的代码后输出结果。
// 默认处理单入口
output: {filename: bundle.js,// 输出⽂文件的名称path: path.resolve(__dirname, dist)// 输出⽂文件到磁盘的⽬目录必须是绝对路路径
},// 多⼊口的处理
output: {filename: [name][chunkhash:8].js,// 利用占位符⽂件名称不要重复path: path.resolve(__dirname, dist)// 输出⽂件到磁盘的目录必须是绝对路径
},mode
mode用来指定当前的构建环境主要取值有
production // 生产环境的开启会有帮助模块压缩处理副作用等一些功能development // 开发环境的开启会有利于热更新的处理识别哪个模块变化none // 5取消了
设置mode可以自动触发webpack内置的函数达到优化的效果 none的赋值在5版本中取消了包括production时启动的函数中TerserPlugin改成为UglifyJsPlugin module
模块在webpack里一切皆模块⼀个模块对应着一个文件。
webpack会从配置的 Entry 开始递归找出所有依赖的模块。当webpack处理到不认识的模块时需要在webpack中的module 处进⾏配置当检测到是什么格式的模块使⽤什么loader来处理。
module:{rules:[{test:/\.xxx$/, //指定匹配规则 use:{loader: xxx-load//指定使⽤用的loader }}]
}loader
webpack 默认只支持.json 和 .js模块不支持不认识其他格式的模块而loader就是用来转换这些不认识模块的模块转换器可以把模块原内容按照需求转换成新内容。
常⻅的loader
style-loader
css-loader
less-loader
sass-loader
ts-loader // 将Ts转换成js
babel-loader // 转换ES6、7等js新特性语法
file-loader // 处理理图⽚片⼦子图
eslint-loader
...Plugins
plugins 选项用于以各种方式自定义 webpack 构建过程。
也就是说webpack的打包过程是有生命周期的概念的每个阶段都有对应的钩子函数而plugin就可以在webpack运行到某个阶段的时候进行一些操作类似生命周期钩子函数的扩展插件最终完成在webpack构建过程中的特定时机注入扩展逻辑来生成自己想要的结果。
Loader详解
上面我们了解到loader是webpack构建过程中的模块转换器下面我们着重看几个常用的loader
静态资源处理类 file-loader file-loader是把打包⼊口中识别出的资源模块移动到输出目录并且返回⼀个地址名称所以file-loader通常用来处理那些只需要从源代码移到打包目录的静态资源比如txt、svg、csv、excel以及各种图片、字体文件。 module: {rules: [// test 表示需要识别文件的后缀名即是这样的后缀名的文件需要由该loader处理test: /\.(png|jpe?g|gif)$/,// use 表示处理上述匹配文件的loader如果是一个loader需要用对象表示如果是多个loader则需要用数组use: {loader: file-loader,options: {// [name]老资源模块的名称 [hash]hash值 [ext]老资源的后缀名name: [name]_[hash].[ext]// 打包后的存放位置outputPath: images/}}]
}url-loader url-loader是file-loader的加强版本它的内部实现也是基于file-loader所以可以处理file-loader可以处理的资源。 同时url-loader会根据默认的或者配置的limit参数将满足条件的图片转换成base64格式并打包到js里从而减小http请求提高页面加载速度。但是这种操作只针对小体积的图片不适用大图片。 module: {rules: [{test: /\.(png|jpe?g|gif)$/,use: {loader: url-loader,options: {name: [name]_[hash].[ext],outputPath: images/,// 小于2048Byte2KB才转换成base64limit: 2048}}}]
}样式处理类 css-loader 分析css模块之间的关系并合成⼀个css style-loader style-loader可以会把css-loader生成的内容以style挂载到页面的head部分 less-loader less-loader会把less语法转成css # 在多loader转换时执行顺序为从右到左从下到上
module: {rules: [{test: /\.css$/,use: [style-loader, css-loader]},{test: /\.scss$/,use: [style-loader, css-loader, less-loader]}]
}postcss-loader postcss-loader也比较常见通过用来批量转化css比如自动添加前缀以适配不同版本的浏览器具体的浏览器适配规则可以参见https://caniuse.com/ // webpack.config.js中的配置
module: {rules: [{test: /\.css$/,use: [style-loader, css-loader, postcss-loader]}]
}// 此外需要新建postcss.config.js
module.exports {plugins: [require(autoprefixer)({// last 2 versions: 最近的两个大版本// 1%: 全球市场份额大于1%的浏览器overrideBrowerslist: [last 2 versions, 1%]})]
}JS脚本处理类
babel-loader
在使用babel-loader我们需要了解几个概念 Babel是JavaScript编译器器能将ES6代码转换成ES5代码让我们开发过程中放⼼使用JS新特性⽽不用担⼼心兼容性问题。并且还可以通过插件机制根据需求灵活的扩展。 babel-loader是webpack与babel的通信桥梁不做把es6转成es5的⼯作这部分⼯作需要用到babel/preset-env来做babel/preset-env里包含了es678转es5的转换规则 在使用preset-env时需要注意的就是按需引入由配置参数useBuiltIns决定useBuiltIns选项是babel7的新功能这个选项告诉babel如何配置babel/polyfill。 它有三个参数可以使⽤: entry需要在webpack的⼊口文件里import babel/polyfill⼀次。 babel会根据你的使⽤情况导⼊垫片没有使⽤的功能不会被导入相应的垫⽚。usage不需要import全⾃动检测但是要安装babel/polyfill。(试验阶段)false如果你import babel/polyfill 它不会排除掉没有使用的垫⽚程序体积会庞⼤。(不推荐) 请注意: usage的⾏为类似 babel-transform-runtime不会造成全局污染因此也不会对类似 Array.prototype.includes() 进行 polyfill。 [babel/preset-env,{targets: { // 需要指定代码运行的浏览器环境edge: 17,firefox: 60, chrome: 67,safari: 11.1},corejs: 2,// 新版本需要指定核心库版本useBuiltIns: usage// 按需注入}
]默认的Babel只⽀持let等⼀些基础的特性转换Promise等⼀些还有转换过来这时候需要借助垫片 babel/polyfill把es的新特性都装进来来弥补低版本浏览器中缺失的特性其原理是语法转换也就是说在转换后的文件里定义一个promise并挂载到window对象上。 Babel在执⾏编译的过程中会从项⽬根⽬录下的 .babelrc JSON 文件中读取配置。没有该⽂件会从loader的options地⽅读取配置 //.babelrc
{presets: [[babel/preset-env,{targets: { // 需要指定代码运行的浏览器环境edge: 17,firefox: 60, chrome: 67,safari: 11.1},corejs: 2,// 新版本需要指定核心库版本useBuiltIns: usage// 按需注入}]]
}//webpack.config.js
{test: /\.js$/,exclude: /node_modules/, loader: babel-loader
}这是最后的全部配置 module: {rules: [test: /\.js$/,exclude: /node_modules/,use: {loader: babel-loader, options: {presets: [[babel/preset-env,{targets: { // 需要指定代码运行的浏览器环境edge: 17,firefox: 60, chrome: 67,safari: 11.1},corejs: 2,// 新版本需要指定核心库版本useBuiltIns: usage// 按需注入}]]}}]
}Plugin详解
HtmlWebpackPlugin
HtmlWebpackPlugin简化了HTML文件的创建以便为你的webpack包提供服务。这对于在文件名中包含每次会随着编译而发生变化哈希的 webpack bundle 尤其有用。 你可以让插件为你生成一个HTML文件使用lodash模板提供你自己的模板或使用你自己的loader。
// 配置参数
title: ⽤来⽣成⻚面的 title 元素
filename: 输出的 HTML ⽂件名默认是 index.html, 也可以直接配置带有子目录。
template: 模板⽂件路路径⽀持加载器⽐如 html!./index.html
inject: true | head | body | false ,注⼊所有的资源到特定的 template 或者 templateContent 中如果设置为 true 或者 body所有的 javascript 资源将被放置到 body 元 素的底部head 将放置到 head 元素中。
favicon: 添加特定的 favicon 路径到输出的 HTML ⽂文件中。 minify: {} | false , 传递 html-minifier 选项给 minify 输出
hash: true | false, 如果为 true, 将添加⼀个唯一的 webpack 编译 hash 到所有包含的脚本和 CSS 文件对于解除 cache 很有用。
cache: true | false如果为 true, 这是默认值仅仅在⽂件修改之后才会发布⽂件。
showErrors: true | false, 如果为 true, 这是默认值错误信息会写入到 HTML ⻚面中。
chunks: 允许只添加某些块 (⽐如仅 unit test 块) chunksSortMode: 允许控制块在添加到⻚面之前的排序方式支持的值:none | default | {function}-default:auto excludeChunks: 允许跳过某些块(比如跳过单元测试的块)// 使用案例
const path require(path)
const htmlWebpackPlugin require(html-webpack-plugin)
module.exports {......plugins: [new htmlWebpackPlugin(// 插件参数传递使用对象{title: my App,filename: index.html,template: ./src/index.html})]
}// index.html 中获取插件参数
title% htmlWebpackPlugin.options.title %/titleCleanWebpackPlugin
其实 clean-webpack-plugin 很容易知道它的作用就是来清除文件的。
一般这个插件是配合 webpack -p 这条命令来使用就是说在为生产环境编译文件的时候先把 build或dist (就是放生产环境用的文件) 目录里的文件先清除干净再生成新的。
// 使用案例
const cleanWebpackPlugin require(clean-webpack-plugin)
module.exports {......plugins: [new cleanWebpackPlugin()]
}MiniCssExtractPlugin
一般我们的 css 是直接打包进 js⾥面的我们希望能单独⽣成 css文件。 因为单独⽣成csscss可以和js并行下载提高⻚面加载效率
借助MiniCssExtractPlugin 完成抽离css
// webpack.config.js
const MiniCssExtractPlugin require(mini-css-extract-plugin);module: {rules: [{test: /\.scss$/,use: [// style-loader, // 不再需要style-loader⽤MiniCssExtractPlugin.loaderMiniCssExtractPlugin.loader,css-loader, // 编译css postcss-loader, sass-loader // 编译scss]}]
},
plugins: [new MiniCssExtractPlugin({filename: css/[name]_[contenthash:6].css,chunkFilename: [id].css })
]性能优化
提升检索速度
1. 缩小loader处理范围
优化loader配置 test include exclude三个配置项来缩小loader的处理范围 推荐include include: path.resolve(__dirname, ./src),2. resolve.modules resolve.modules⽤于配置webpack去哪些目录下寻找第三⽅模块默认是[‘node_modules’] 寻找第三方模块默认是在当前项⽬录下的node_modules⾥⾯去找如果没有找到就会去上⼀级目录…/node_modules找再没有会去…/…/node_modules中找以此类推和Node.js的模块寻找机制很类似。 如果我们的第三方模块都安装在了项目根⽬录下就可以直接指明这个路径。 module.exports {resolve:{modules: [path.resolve(__dirname, ./node_modules)] }
}3. resolve.alias
resolve.alias配置通过别名来将原导⼊路径映射成⼀个新的导⼊路径
拿react为例我们引⼊的react库⼀般存在两套代码 cjs 采⽤用commonJS规范的模块化代码 umd 已经打包好的完整代码没有采用模块化可以直接执⾏ 默认情况下webpack会从⼊口文件./node_modules/bin/react/index开始递归解析和处理依赖的⽂件。我们可以直接指定文件避免这处的耗时。 alias: {: path.join(__dirname, ./pages),react: path.resolve(__dirname, ./node_modules/react/umd/react.production.min.js),react-dom: path.resolve(__dirname, ./node_modules/react-dom/umd/react-dom.production.min.js)
}4. resolve.extensions
resolve.extensions在导入语句没带文件后缀时webpack会⾃动带上后缀后去尝试查找⽂件是否存在。
默认值:
extensions:[.js,.json,.jsx,.ts]后缀尝试列表尽量的⼩导⼊语句尽量的带上后缀
5. externals
我们可以将⼀一些JS⽂文件存储在 CDN 上(减少 Webpack 打包出来的 js 体积)在 index.html 中通过 标签引⼊入如:
!DOCTYPE html
html langen
headmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0meta http-equivX-UA-Compatible contentieedge titleDocument/title
/head
bodydiv idrootroot/divscript srchttp://libs.baidu.com/jquery/2.0.0/jquery.min.js/script
/body
/html我们希望在使用时仍然可以通过 import 的⽅式去引⽤(如 import $ from ‘jquery’ )并且希望 webpack 不不会对其进⾏打包此时就可以配置 externals 。
//webpack.config.js
module.exports {//...externals: {//jquery通过script引⼊入之后全局中即有了了 jQuery 变量量 jquery: jQuery}
}6. CDN
CDN通过将资源部署到世界各地使得⽤户可以就近访问资源加快访问速度。要接⼊CDN需要把⽹
⻚的静态资源上传到CDN服务上在访问这些资源时使⽤CDN服务提供的URL。
// webpack.config.js
output:{publicPath: //cdnURL.com, //指定存放JS⽂文件的CDN地址
}有cdn服务器地址确保静态资源⽂件的上传与否
提升加载速度
1. css压缩 借助 optimize-css-assets-webpack-plugin 借助 cssnano # 安装
npm install cssnano -D
npm i optimize-css-assets-webpack-plugin -D// webpack.config.js
const OptimizeCSSAssetsPlugin require(optimize-css-assets-webpack-plugin);
new OptimizeCSSAssetsPlugin({cssProcessor: require(cssnano), //引⼊cssnano配置压缩选项 cssProcessorOptions: {discardComments: { removeAll: true }}
})2. html压缩 借助html-webpack-plugin new htmlWebpackPlugin({ title: my app,template: ./index.html, filename: index.html, minify: {// 压缩HTML⽂文件removeComments: true, // 移除HTML中的注释 collapseWhitespace: true, // 删除空⽩符与换行符 minifyCSS: true // 压缩内联css}
}),3. 摇树tree Shaking
webpack2.x开始支持 tree shaking概念顾名思义“摇树”清除⽆用 css,js(Dead Code)
Dead Code ⼀般具有以下⼏几个特征
代码不会被执⾏不可到达代码执⾏的结果不会被用到代码只会影响死变量(只写不读)Js tree shaking只支持ES module的引⼊方式
css tree shaking
npm i glob-all purify-css purifycss-webpack --save-devconst PurifyCSS require(purifycss-webpack)
const glob require(glob-all)
plugins:[// 清除⽆用 css new PurifyCSS({paths: glob.sync([// 要做 CSS Tree Shaking 的路径⽂件path.resolve(__dirname, ./src/*.html), // 请注意我们同样需要对html⽂件进行 tree shakingpath.resolve(__dirname, ./src/*.js)])})
]Js tree shaking 只支持import方式引入不支持commonjs的方式引入 只要mode是production就会生效develpoment的tree shaking是不生效的因为webpack为了方便你的调试 可以查看打包后的代码注释以辨别是否⽣效 生产模式不需要配置默认开启 // webpack.config.js
optmization: {usedExports: true // 只要导出的模块被使用了再做打包
}副作用
// package.json
sideEffects:false //正常对所有模块进⾏行行tree shaking , 仅⽣生产模式有效需要配合 usedExports 或者 在数组⾥里里⾯面排除不不需要tree shaking的模块
sideEffects:[*.css,babel/polyfill]4. 代码分割code Splitting
单页面应用spa
打包完后所有页面只生成一个bundle.js
代码体积变大不利于下载没有合理利用浏览器资源
多页面应用mpa
如果多个页面引入了一些公共模块那么可以把这些公共的模块抽离出来单独打包。公共代码只需要下载一次就缓存起来避免重复下载。
配置
其实code Splitting概念 与 webpack并没有直接的关系只不过webpack中提供了一种更加方便的方法供我们实现代码分割基于https://webpack.js.org/plugins/split-chunks-plugin/
optimization: {splitChunks: {chunks: async,// 对同步 initial异步 async所有的模块有效 allminSize: 30000,// 最⼩尺寸当模块大于30kbmaxSize: 0,// 对模块进行二次分割时使⽤用不推荐使⽤minChunks: 1,// 打包⽣成的chunk⽂件最少有几个chunk引⽤了这个模块maxAsyncRequests: 5,// 最⼤异步请求数默认5 maxInitialRequests: 3,// 最⼤初始化请求书⼊口文件同步请求默认3 automaticNameDelimiter: -,// 打包分割符号name: true,// 打包后的名称除了布尔值还可以接收⼀个函数function cacheGroups: {// 缓存组vendors: {test: /[\\/]node_modules[\\/]/, name:vendor,// 要缓存的分隔出来的 chunk 名称 priority: -10// 缓存组优先级数字越大优先级越⾼}, other:{chunks: initial,// 必须三选⼀: initial|all|async(默认test: /react|lodash/,// 正则规则验证如果符合就提取 chunk, name:other,minSize: 30000,minChunks: 1,},default: {minChunks: 2,priority: -20,reuseExistingChunk: true// 可设置是否重⽤用该chunk}}}
}平时使用下面的配置即可
optimization:{ // 帮我们自动做代码分割 splitChunks:{chunks:all,// 默认是⽀持异步我们使用all }
}5. 作⽤域提升Scope Hoisting
作用域提升(Scope Hoisting)是指 webpack 通过 ES6 语法的静态分析分析出模块之间的依赖关系尽可能地把模块放到同一个函数中。下⾯通过代码示例来理理解:
// hello.js
export default Hello, Webpack; // index.js
import str from ./hello.js;
console.log(str);打包后 hello.js 的内容和 index.js 会分开 通过配置 optimization.concatenateModulestrue:开启 Scope Hoisting
// webpack.config.js
module.exports { optimization: {concatenateModules: true}
};我们发现hello.js内容和index.js的内容合并在一起了所以通过 Scope Hoisting 的功能可以让 Webpack 打包出来的代码文件更小、运行的更快
6. 动态链接库DllPlugins
Dll动态链接库 其实就是做缓存
项目中引入了很多第三方库这些库在很⻓的⼀段时间内基本不会更新打包的时候分开打包来提升打包速度⽽DllPlugin动态链接库插件其原理就是把⽹⻚依赖的基础模块抽离出来打包到dll文件中 当需要导入的模块存在于某个dll中时这个模块不再被打包⽽是去dll中获取。
动态链接库只需要被编译⼀次项⽬中⽤到的第三方模块很稳定例如react,react-dom只要没有升级的需求webpack已经内置了对动态链接库的⽀持DllPlugin⽤于打包出⼀个单独的动态链接库文件DllReferencePlugin用于在主要的配置⽂件中引⼊DllPlugin插件打包好的动态链接库⽂件
新建webpack.dll.config.js⽂文件打包基础模块
我们在 index.js 中使⽤了第三方库 react 、 react-dom 接下来我们先对这两个库先进行打包。
// webpack.dll.config.js
const path require(path);
const { DllPlugin } require(webpack);
module.exports {mode: development, entry: {react: [react, react-dom] //! node_modules? },output: {path: path.resolve(__dirname, ./dll),filename: [name].dll.js,library: react}, plugins: [new DllPlugin({// manifest.json⽂文件的输出位置path: path.join(__dirname, ./dll, [name]-manifest.json), // 定义打包的公共vendor⽂文件对外暴暴露露的函数名name: react})]
}在package.json中添加
dev:dll: webpack --config ./build/webpack.dll.config.js,运行
npm run dev:dll你会发现多了一个dll⽂文件夹⾥边有dll.js文件这样我们就把我们的React这些已经单独打包了
dll⽂件包含了大量模块的代码这些模块被存放在⼀个数组里。⽤数组的索引号为ID,通过变量将⾃己暴露在全局中就可以在window.xxx访问到其中的模块Manifest.json 描述了与其对应的dll.js包含了哪些模块以及ID和路径
打包好之后需要将dll文件注入index.html中使用依赖add-asset-html-webpack-plugin
// webpack.config.js
new AddAssetHtmlWebpackPlugin({filepath: path.resolve(__dirname, ../dll/react.dll.js) // 对应的 dll ⽂件路径
})运行项目
npm run dev这个理解起来不费劲操作起来很费劲。所幸在Webpack5中已经不用它了而是⽤ HardSourceWebpackPlugin ⼀样的优化效果但是使用却及其简单 提供中间缓存的作⽤ ⾸次构建没有太大的变化 第⼆次构建时间就会有较大的节省 // webpack.config.js
const HardSourceWebpackPlugin require(hard-source-webpack-plugin)
const plugins [new HardSourceWebpackPlugin()
]7. 并发任务happypack
运行在 Node.之上的Webpack是单线程模型的也就是说Webpack需要⼀个一个地处理任务不能同时处理多个任务。 Happy Pack 就能让Webpack做到这一点它将任务分解给多个⼦进程去并发执行⼦进程处理完后再将结果发送给主进程。从⽽发挥多核CPU电脑的威力。
// webpack.config.js
var happyThreadPool HappyPack.ThreadPool({ size: 5 });// module中添加
rules: [ {test: /\.jsx?$/, exclude: /node_modules/, use: [{loader: happypack/loader?idbabel }] },{test: /\.css$/,include: path.resolve(__dirname, ./src), use: [happypack/loader?idcss]},
]
//在plugins中增加
plugins:[new HappyPack({// ⽤唯一的标识符id来代表当前的HappyPack是⽤来处理⼀类特定的文件 id:babel,// 如何处理理.js⽂文件⽤用法和Loader配置中⼀一样 loaders:[babel-loader?cacheDirectory],threadPool: happyThreadPool,}),new HappyPack({id: css,loaders: [style-loader, css-loader] }),
]原理剖析
自定义webpack
我们以实现一个简易的mini-webpack为目的来串联一下webpack的打包原理。 首先定义一个webpack类 这个类需要有接收webpack.config.js这样的配置文件这个类需要有一个执行构建的主方法可以通过new webpack(options).start()开始构建 class webpack {// 定义构造函数constructor(options) {// 获取配置文件中的入口和出口const {entry, output} optionsthis.entry entrythis.output outputthis.modules [] // 定义数组用来存放入口模块和其所有的依赖模块}// 开始构建的主方法start() {}
}解析入口文件获取语法结构树AST 这里会涉及到parser.parse方法 const ast parser.parse(entryInfo, {sourceType: module
})找到所有的依赖模块 这里会涉及到babel/core中的traverse方法并且只需要找到type为ImportDeclaration的节点 traverse(ast, {ImportDeclaration({ node }) {}
})将AST转化为code 将 AST 语法树转换为浏览器可执行代码,我们这里使用babel/core中的transformFromAst 和 babel/preset-env。 // 提取内容转化处理
const { code } transformFromAst(ast, null, {presets: [babel/preset-env]
})递归查看所有依赖项 递归查看的时候需要利用的技巧就是利用动态数组的长度遍历一边push一边foreach // 1. 获取入口文件
console.log(--------------1--------------)
const entryInfo this.parse(this.entry)
this.modules.push(entryInfo)// 2. 递归分析其他模块
console.log(--------------2--------------)
for(let i 0; i this.modules.length; i) {const item this.modules[i]const { denpendencies } item // 获取依赖关系if (denpendencies) {for (let j in denpendencies) {this.modules.push(this.parse(denpendencies[j]))}}
}重写require函数输出bundle 这块需要注意的就是引入模块的相对路径转化绝对路径闭包和自执行函数的运用 (function(obj){function require(module){// 将相对地址转成绝对地址并获取function reRequire(relativePath){return require(obj[module].denpendecies[relativePath])}var exports {}(function(require, exports, code) {eval(code)})(reRequire, exports, obj[module].code)return exports}require(${this.entry})
})(${JSON.stringify(obj)})完整的mini-webpack代码 // mini-webpack.js
const fs require(fs)
const path require(path)
const parser require(babel/parser)
const traverse require(babel/traverse).default
const { transformFromAst } require(babel/core)// 导出一个打包类通过 new webpack(options).start() 进行构建
module.exports class webpack {// 定义构造函数constructor(options) {// 获取配置文件中的入口和出口const {entry, output} optionsthis.entry entrythis.output outputthis.modules [] // 定义数组用来存放入口模块和其所有的依赖模块}// 开始构建的主方法start() {console.log(开始构建。。。。。。)// 1. 获取入口文件console.log(--------------1--------------)const entryInfo this.parse(this.entry)this.modules.push(entryInfo)// 2. 递归分析其他模块console.log(--------------2--------------)for(let i 0; i this.modules.length; i) {const item this.modules[i]const { denpendencies } item // 获取依赖关系if (denpendencies) {for (let j in denpendencies) {this.modules.push(this.parse(denpendencies[j]))}}}console.log(this.modules);// 3. 结构转化 {文件名: {依赖代码}}console.log(--------------3--------------)const obj {}this.modules.forEach((item) {obj[item.entryFile] {denpendencies: item.denpendencies,code: item.code}})console.log(obj)// 4. 生成bundle文件console.log(--------------4--------------)this.generate(obj)}// 根据入口文件获取依赖和内容代码parse(entryFile) {// 获取到index.js中的文本内容const entryInfo fs.readFileSync(entryFile, utf-8)// 根据内容原文获取抽象语法树AST ast.program.body才是内容的主体const ast parser.parse(entryInfo, {sourceType: module})// 提取依赖转化处理const denpendencies {}; // 用来记录依赖的相对地址和绝对地址的对应关系traverse(ast, {// 提取type为ImportDeclaration的节点nodenode.source.value为import()的参数ImportDeclaration({ node }) {// 通过node.source.value获取为相对路径需要转换为绝对地址并和相对路径一一对应const prefixPath ./ // 这里是Mac系统的地址类型不兼容Windows和Linuxdenpendencies[node.source.value] prefixPath path.join(path.dirname(entryFile), node.source.value)console.log(denpendencies)}})// 提取内容转化处理const { code } transformFromAst(ast, null, {presets: [babel/preset-env]})console.log(code)return {entryFile,denpendencies,code}}// 根据所有模块及其依赖和代码生成bundlegenerate(obj) {// 根据output参数获取bundle存放路径const filePath path.join(this.output.path, this.output.filename)// 创建bundle的内容内容主要有// 创建自执行函数处理require module exports//const bundle (function(obj){function require(module){function reRequire(relativePath){return require(obj[module].denpendecies[relativePath])}var exports {}(function(require, exports, code) {eval(code)})(reRequire, exports, obj[module].code)return exports}require(${this.entry})})(${JSON.stringify(obj)})fs.writeFileSync(filePath, bundle, utf-8)}
}结语
关于webpack的就写到这里。其实工具对于我们研发人员来说就是提升效率的一个途径这样的途径有很多中比如有gulp、rollup、grant还有百度fis等等都各有千秋用好了可以事半功倍用不好反而事倍功半。
以上就是我对webpack使用过程中的简要总结希望能帮到大家。同时有不恰之处还望大家批评指正。
最后喜欢我的小伙伴也可以通过关注公众号“剑指大前端”或者扫描下方二维码联系到我进行经验交流和分享同时我也会定期分享一些大前端干货让我们的开发从此不迷路。
关注后回复“webpack”即可获取最新webpack讲解教学视频哦