从零开始配置webpack(优化篇)
文章目录
前言
从零开始配置webpack系列(基础篇)
从零开始配置webpack系列(进阶篇)
从零开始配置webpack系列(优化篇)
从零开始配置webpack系列(原理篇)
这里是小飞侠Pan🥳,立志成为一名优秀的前端程序媛!!!
本篇博客收录于我的github前端笔记仓库中,欢迎star~
👉https://github.com/mengqiuleo/myNote
提升开发体验
source-map
无论是什么开发,要求开发环境最不可少的一点功能就是 ——debug功能。
之前我们通过webpack, 将我们的源码打包成了 bundle.js。 试想:实际上客户端(浏览器)读取的是打包后的 bundle.js ,那么当浏览器执行代码报错的时候,报错的信息自然也是bundle的内容。我们如何将报错信息(bundle错误 的语句及其所在行列)映射到源码上?souce-map
webpack已经内置了sourcemap的功能,我们只需要通过简单的配置,将可以开启它。
module.exports ={// 开启 source map// 开发中推荐使用 'source-map'// 生产环境一般不开启 sourcemapdevtool:'source-map',}
当我们执行打包命令之后,我们发现bundle的最后一行总是会多出一个注释,指向 打包出的bundle.map.js(sourcemap文件)。 sourcemap文件用来描述 源码文件和 bundle文件的代码位置映射关系。基于它,我们将bundle文件的错误信息映射到源 码文件上。
除开’source-map’外,还可以基于我们的需求设置其他值,webpack——devtool一 共提供了7种SourceMap模式:
模式解释eval每个module会封装到 eval 里包裹起来执行,并且会在末尾追 加注释 //@ sourceURL.source-map生成一个SourceMap文件.hidden-source-map和 source-map 一样,但不会在 bundle 末尾追加注释.inline-source-map生成一个 DataUrl 形式的 SourceMap 文件eval-source-map每个module会通过eval()来执行,并且生成一个DataUrl形式的 SourceMap.cheap-source-map生成一个没有列信息(column-mappings)的SourceMaps文 件,不包含loader的 sourcemap(譬如 babel 的 sourcemap)cheap-module-source-map生成一个没有列信息(column-mappings)的SourceMaps文 件,同时 loader 的 sourcemap 也被简化为只包含对应行的
要注意的是,生产环境我们一般不会开启sourcemap功能,
主要有两点原因:
- 通过bundle和sourcemap文件,可以反编译出源码————也就是说,线上产 物有soucemap文件的话,就意味着有暴漏源码的风险。
- 可以观察到,sourcemap文件的体积相对比较巨大,这跟我们生产环境的追 求不同(生产环境追求更小更轻量的bundle)
提升打包构建速度
HotModuleReplacement(模块热替换与热加载)
模块热替换
模块热替换(HMR - hot module replacement)功能会在应用程序运行过程中, 替换、添加或删除模块,而无需重新加载整个页面
启用 webpack 的 热模块替换 特性,需要配置devServer.hot参数
module.exports ={//...devServer:{hot:true,},};
此时我们实现了基本的模块热替换功能。
模块热加载
热加载(文件更新时,自动刷新我们的服务和页面) 新版的webpack-dev-server 默认已经开启了热加载的功能。 它对应的参数是devServer.liveReload,默认为 true。 注意,如果想要关掉它,要将liveReload设置为false的同时,也要关掉 hot
module.exports ={//...devServer:{liveReload:false,//默认为true,即开启热更新功能。},};
OneOf
打包时每个文件都会经过所有 loader 处理,虽然因为
test
正则原因实际没有处理上,但是都要过一遍。比较慢。
OneOf 就是只能匹配上一个 loader, 剩下的就不匹配了。
使用:
const path =require("path");const ESLintWebpackPlugin =require("eslint-webpack-plugin");const HtmlWebpackPlugin =require("html-webpack-plugin");
module.exports ={entry:"./src/main.js",output:{path:undefined,// 开发模式没有输出,不需要指定输出目录filename:"static/js/main.js",// 将 js 文件输出到 static/js 目录中// clean: true, // 开发模式没有输出,不需要清空输出结果},module:{rules:[{oneOf:[{// 用来匹配 .css 结尾的文件test:/\.css$/,// use 数组里面 Loader 执行顺序是从右到左use:["style-loader","css-loader"],},{test:/\.(png|jpe?g|gif|webp)$/,type:"asset",parser:{dataUrlCondition:{maxSize:10*1024,// 小于10kb的图片会被base64处理},},generator:{// 将图片文件输出到 static/imgs 目录中// 将图片文件命名 [hash:8][ext][query]// [hash:8]: hash值取8位// [ext]: 使用之前的文件扩展名// [query]: 添加之前的query参数filename:"static/imgs/[hash:8][ext][query]",},},{test:/\.(ttf|woff2?)$/,type:"asset/resource",generator:{filename:"static/media/[hash:8][ext][query]",},},{test:/\.js$/,exclude:/node_modules/,// 排除node_modules代码不编译loader:"babel-loader",},],},],},plugins:[newESLintWebpackPlugin({// 指定检查文件的根目录context: path.resolve(__dirname,"../src"),}),newHtmlWebpackPlugin({// 以 public/index.html 为模板创建文件// 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源template: path.resolve(__dirname,"../public/index.html"),}),],// 开发服务器devServer:{host:"localhost",// 启动服务器域名port:"3000",// 启动服务器端口号open:true,// 是否自动打开浏览器hot:true,// 开启HMR功能},mode:"development",devtool:"cheap-module-source-map",};
Include/Exclude
开发时我们需要使用第三方的库或插件,所有文件都下载到 node_modules 中了。而这些文件是不需要编译可以直接使用的。
所以我们在对 js 文件处理时,要排除 node_modules 下面的文件。
- include
包含,只处理 xxx 文件
- exclude
排除,除了 xxx 文件以外其他文件都处理
module.exports ={module:{rules:[{oneOf:[{test:/\.js$/,// exclude: /node_modules/, // 排除node_modules代码不编译include: path.resolve(__dirname,"../src"),// 也可以用包含loader:"babel-loader",},],},],},plugins:[newESLintWebpackPlugin({// 指定检查文件的根目录context: path.resolve(__dirname,"../src"),exclude:"node_modules",// 默认值}),],};
Cache
每次打包时 js 文件都要经过 Eslint 检查 和 Babel 编译,速度比较慢。
我们可以缓存之前的 Eslint 检查 和 Babel 编译结果,这样第二次打包时速度就会更快了
Cache 对 Eslint 检查 和 Babel 编译结果进行缓存。
module.exports ={module:{rules:[{oneOf:[{test:/\.js$/,// exclude: /node_modules/, // 排除node_modules代码不编译include: path.resolve(__dirname,"../src"),// 也可以用包含loader:"babel-loader",options:{cacheDirectory:true,// 开启babel编译缓存cacheCompression:false,// 缓存文件不要压缩},},],},],},};
减少代码体积
Tree Shaking
开发时我们定义了一些工具函数库,或者引用第三方工具函数库或组件库。
如果没有特殊处理的话我们打包时会引入整个库,但是实际上可能我们可能只用上极小部分的功能。
这样将整个库都打包进来,体积就太大了
Tree Shaking
是一个术语,通常用于描述移除 JavaScript 中的没有使用上的代码。
**注意:它依赖
ES Module
**
Webpack 已经默认开启了这个功能,无需其他配置
sideEffects
Webpack 不能百分百安全地进行 tree-shaking。有些模块导入,只要被引入,就会对应用程序产生重要的影响。一个很好的例子就是全局样式表,或者设置全局配 置的JavaScript 文件。
Webpack 认为这样的文件有“副作用”。具有副作用的文件不应该做 tree-shaking, 因为这将破坏整个应用程序
如何告诉 Webpack 你的代码无副作用,可以通过 package.json 有一个特殊的属性 sideEffects,就是为此而存在的。
它有三个可能的值:
- true 如果不指定其他值的话。这意味着所有的文件都有副作用,也就是没有一个文件 可以 tree-shaking。
- false 告诉 Webpack 没有文件有副作用,所有文件都可以 tree-shaking。
- 数组[…] 是文件路径数组。它告诉 webpack,除了数组中包含的文件外,你的任何文件都没有副作用。因此,除了指定的文件之外,其他文件都可以安全地进行 treeshaking。
优化代码运行性能
Network Cache
将来开发时我们对静态资源会使用缓存来优化,这样浏览器第二次请求资源就能读取缓存了,速度很快。
但是这样的话就会有一个问题, 因为前后输出的文件名是一样的,都叫 main.js,一旦将来发布新版本,因为文件名没有变化导致浏览器会直接读取缓存,不会加载新资源,项目也就没法更新了。
所以我们从文件名入手,确保更新前后文件名不一样,这样就可以做缓存了
- fullhash(webpack4 是 hash)
每次修改任何一个文件,所有文件名的 hash 至都将改变。所以一旦修改了任何一个文件,整个项目的文件缓存都将失效。
- chunkhash
根据不同的入口文件(Entry)进行依赖文件解析、构建对应的 chunk,生成对应的哈希值。我们 js 和 css 是同一个引入,会共享一个 hash 值。
- contenthash
根据文件内容生成 hash 值,只有文件内容变化了,hash 值才会变化。所有文件 hash 值是独享且不同的
// [contenthash:8]使用contenthash,取8位长度filename:"static/js/[name].[contenthash:8].js",// 入口文件打包输出资源命名方式chunkFilename:"static/js/[name].[contenthash:8].chunk.js",// 动态导入输出资源命名方式
Core-js
过去我们使用 babel 对 js 代码进行了兼容性处理,其中使用@babel/preset-env 智能预设来处理兼容性问题。
它能将 ES6 的一些语法进行编译转换,比如箭头函数、点点点运算符等。但是如果是 async 函数、promise 对象、数组的一些方法(includes)等,它没办法处理。
所以此时我们 js 代码仍然存在兼容性问题,一旦遇到低版本浏览器会直接报错。所以我们想要将 js 兼容性问题彻底解决
core-js
是专门用来做 ES6 以及以上 API 的
polyfill
。
polyfill
翻译过来叫做垫片/补丁。就是用社区上提供的一段代码,让我们在不兼容某些新特性的浏览器上,使用该新特性
首先修改js文件,使用promise:
import"core-js/es/promise";//手动按需引入, import "core-js";是全部引入,体积较大import count from"./js/count";import sum from"./js/sum";const result1 =count(2,1);
console.log(result1);const result2 =sum(1,2,3,4);
console.log(result2);// 添加promise代码const promise = Promise.resolve();
promise.then(()=>{
console.log("hello promise");});
npm i core-js
配置:
module.exports ={// 智能预设:能够编译ES6语法presets:[["@babel/preset-env",// 按需加载core-js的polyfill{useBuiltIns:"usage",corejs:{version:"3",proposals:true}},],],};
此时就会自动根据我们代码中使用的语法,来按需加载相应的
polyfill
了
PWA
开发 Web App 项目,项目一旦处于网络离线情况,就没法访问了。我们希望给项目提供离线体验。
渐进式网络应用程序(progressive web application - PWA):是一种可以提供类似于 native app(原生应用程序) 体验的 Web App 的技术。
其中最重要的是,在 离线(offline) 时应用程序能够继续运行功能。
内部通过 Service Workers 技术实现的
npm i workbox-webpack-plugin -D
修改配置文件:
const WorkboxPlugin =require("workbox-webpack-plugin");
module.exports ={plugins:[newWorkboxPlugin.GenerateSW({// 这些选项帮助快速启用 ServiceWorkers// 不允许遗留任何“旧的” ServiceWorkersclientsClaim:true,skipWaiting:true,}),]}
修改main.js
import count from"./js/count";import sum from"./js/sum";const result1 =count(2,1);
console.log(result1);const result2 =sum(1,2,3,4);
console.log(result2);// 添加promise代码const promise = Promise.resolve();
promise.then(()=>{
console.log("hello promise");});const arr =[1,2,3,4,5];
console.log(arr.includes(5));// 配置 PWAif("serviceWorker"in navigator){
window.addEventListener("load",()=>{
navigator.serviceWorker
.register("/service-worker.js").then((registration)=>{
console.log("SW registered: ", registration);}).catch((registrationError)=>{
console.log("SW registration failed: ", registrationError);});});}
版权归原作者 小飞侠Pan 所有, 如有侵权,请联系我们删除。