资讯专栏INFORMATION COLUMN

webpack4.0优化那些事儿

levy9527 / 3424人阅读

摘要:配置以何种方式导出库。当检测文件不再发生变化,会先缓存起来,等等待一段时间后之后再通知监听者,这个等待时间通过配置。发布到线上给用户使用的运行环境。

一 缩小文件搜索范围 1 include & exclude 1) action

限制编译范围

2) useage

</>复制代码

  1. module: {
  2. rules: [
  3. {
  4. test: /.js$/,
  5. use: ["babel-loader?cacheDirectory"],
  6. include: path.resolve(__dirname, "src"),
  7. exclude: /node_modules/
  8. }
  9. ]
  10. }

</>复制代码

  1. "babel-loader?cacheDirectory"

You can also speed up babel-loader by as much as 2x by using the cacheDirectory option. This will cache transformations to the filesystem.

QA
命令行warning

</>复制代码

  1. [BABEL] Note: The code generator has deoptimised the styling of "/Users/xxx/Documents/xxx/webpack_test/test3/node_modules/lodash/lodash.js" as it exceeds the max of "500KB".

加上exclude限制范围就不会报错了

2 resolve.modules 1) action

Tell webpack what directories should be searched when resolving modules.

2) useage

</>复制代码

  1. resolve: {
  2. modules: [path.resolve("node_modules"), path.resolve("lib")]
  3. }
3) note

Absolute and relative paths can both be used, but be aware that they will behave a bit differently.

A relative path will be scanned similarly to how Node scans for node_modules, by looking through the current directory as well as it"s ancestors (i.e. ./node_modules, ../node_modules, and on).

With an absolute path, it will only search in the given directory.

If you want to add a directory to search in that takes precedence over node_modules/:(即是有先后顺序的)

</>复制代码

  1. modules: [path.resolve(__dirname, "src"), "node_modules"]
4) QA

</>复制代码

  1. Module not found: Error: Can"t resolve "ajax" in "/Users/xxx/Documents/xxx/webpack_test/test3/src"

当你需要指定除node_modules之外的其它模块目录的时候可以在数组中添加属性

3 resolve.mainFields 1) action

Webpack 配置中的 resolve.mainFields 用于配置第三方模块使用哪个入口文件。

安装的第三方模块中都会有一个 package.json文件,用于描述这个模块的属性,其中有些字段用于描述入口文件在哪里,resolve.mainFields 用于配置采用哪个字段作为入口文件的描述。

可以存在多个字段描述入口文件的原因是因为有些模块可以同时用在多个环境中,针对不同的运行环境需要使用不同的代码。 以 isomorphic-fetch API 为例,它是 Promise的一个实现,但可同时用于浏览器和 Node.js 环境。

2) useage

为了减少搜索步骤,在你明确第三方模块的入口文件描述字段时,你可以把它设置的尽量少。 由于大多数第三方模块都采用 main字段去描述入口文件的位置,可以这样配置 Webpack:

</>复制代码

  1. module.exports = {
  2. resolve: {
  3. // 只采用 main 字段作为入口文件描述字段,以减少搜索步骤
  4. mainFields: ["main"],
  5. },
  6. };
4 resolve.alias 1) action

配置项通过别名来把原导入路径映射成一个新的导入路径

此优化方法会影响使用Tree-Shaking去除无效代码

2) useage

</>复制代码

  1. alias: {
  2. "bootstrap": "bootstrap/dist/css/bootstrap.css"
  3. }
5 resolve.extensions 1) action

在导入语句没带文件后缀时,Webpack会自动带上后缀后去尝试询问文件是否存在 -

默认后缀是 extensions: [".js", ".json"]

2) useage 3) note

后缀列表尽可能小

频率最高的往前方

导出语句里尽可能带上后缀

6 module.noParse 1) action

module.noParse 配置项可以让 Webpack 忽略对部分没采用模块化的文件的递归解析处理

2) useage

</>复制代码

  1. module: {
  2. noParse: [/react.min.js/]
  3. }
2) note

</>复制代码

  1. 被忽略掉的文件里不应该包含 importrequire 、 define 等模块化语句
二 DLL 1 action

dll 为后缀的文件称为动态链接库,在一个动态链接库中可以包含给其他模块调用的函数和数据

把基础模块独立出来打包到多带带的动态连接库里

当需要导入的模块在动态连接库里的时候,模块不能再次被打包,而是去动态连接库里获取

2 usage

</>复制代码

  1. 定义插件(DLLPlugin) ---> 引用插件(DllReferencePlugin)

本次例子用jquery举例

1) 定义DLL

用于打包出一个个动态连接库

需要多带带build

</>复制代码

  1. webpack.jquery.config.js

</>复制代码

  1. module.exports = {
  2. entry: ["jquery"],
  3. output: {
  4. filename: "vendor.js",
  5. path: path.resolve(__dirname, "dist"),
  6. libraryTarget: "var",// 打包的方式,hou
  7. library: "vendor_lib_vendor"// DLL的名字
  8. },
  9. plugins: [
  10. new webpack.DllPlugin({
  11. name: "vendor_lib_vendor",// 定义DLL
  12. path: path.resolve(__dirname, "dist/vendor-manifest.json")
  13. })
  14. ]
  15. };

</>复制代码

  1. package.jsonscripts添加

</>复制代码

  1. "dll": "webpack --config webpack.jquery.config.js --mode development"

配置好上述的文件后,在终端运行 npm run dll,时候会在dist目录下生成两个文件,分别是vendor.jsvendor-manifest.jsonvendor.js包含的就是打包后的jquery文件代码,vendor-manifest.json是用来做关联的。DLL定义好了,接下来就是应用打包好的DLL了。

2) 引用DLL

</>复制代码

  1. webpack.config.js 配置文件中引入DllPlugin插件打包好的动态连接库

</>复制代码

  1. plugins: [
  2. new webpack.DllReferencePlugin({
  3. context: __dirname,
  4. manifest: require("./dist/vendor-manifest.json")
  5. })
  6. ],
3) 使用DLL

</>复制代码

  1. app.html
    app.html的底部添加

</>复制代码

3 note

</>复制代码

  1. libraryTarget library

当用 Webpack 去构建一个可以被其他模块导入使用的库时需要用到它们。

output.libraryTarget 配置以何种方式导出库。

output.library 配置导出库的名称。 它们通常搭配在一起使用。

output.libraryTarget 是字符串的枚举类型,支持以下配置。

1) var (默认)

编写的库将通过 var 被赋值给通过 library 指定名称的变量。

假如配置了 output.library="LibraryName",则输出和使用的代码如下:

</>复制代码

  1. // Webpack 输出的代码
  2. var LibraryName = lib_code; //其中 lib_code 代指导出库的代码内容,是有返回值的一个自执行函数。
  3. // 使用库的方法
  4. LibraryName.doSomething();
2) commonjs2

编写的库将通过 CommonJS 规范导出。

假如配置了 output.library="LibraryName",则输出和使用的代码如下:

</>复制代码

  1. // Webpack 输出的代码
  2. exports["LibraryName"] = lib_code;
  3. // 使用库的方法
  4. require("library-name-in-npm")["LibraryName"].doSomething();
  5. // 其中 library-name-in-npm 是指模块发布到 Npm 代码仓库时的名称。
3) commonjs2

编写的库将通过 CommonJS2 规范导出,输出和使用的代码如下:

</>复制代码

  1. // Webpack 输出的代码
  2. module.exports = lib_code;
  3. // 使用库的方法
  4. require("library-name-in-npm").doSomething();

CommonJS2 和 CommonJS 规范很相似,差别在于 CommonJS 只能用 exports 导出,而 CommonJS2 在 CommonJS 的基础上增加了 module.exports 的导出方式。
在 output.libraryTarget 为 commonjs2 时,配置 output.library 将没有意义。

4) this

编写的库将通过 this 被赋值给通过 library 指定的名称,输出和使用的代码如下:

</>复制代码

  1. // Webpack 输出的代码
  2. this["LibraryName"] = lib_code;
  3. // 使用库的方法
  4. this.LibraryName.doSomething();
5) window

编写的库将通过 window 被赋值给通过 library 指定的名称,即把库挂载到 window 上,输出和使用的代码如下:

</>复制代码

  1. // Webpack 输出的代码
  2. window["LibraryName"] = lib_code;
  3. // 使用库的方法
  4. window.LibraryName.doSomething();
6) global

编写的库将通过 global 被赋值给通过 library 指定的名称,即把库挂载到 global 上,输出和使用的代码如下:

</>复制代码

  1. // Webpack 输出的代码
  2. global["LibraryName"] = lib_code;
  3. // 使用库的方法
  4. global.LibraryName.doSomething();
三 HappyPack 1 action

HappyPack就能让Webpack把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程。

2 usage

</>复制代码

  1. install

由于webpack 4.0 刚刚发布,响应的插件还没有更新完,不过可以在后面加一个@next来安装即将发布的版本

</>复制代码

  1. npm i happypack@next -D

</>复制代码

  1. webpack.config.js

</>复制代码

  1. module: {
  2. rules: [
  3. {
  4. test: /.css$/,
  5. use: "happypack/loader?id=css",
  6. //把对.js文件的处理转交给id为babel的HappyPack实例
  7. //用唯一的标识符id来代表当前的HappyPack是用来处理一类特定文件
  8. include: path.resolve("./src"),
  9. exclude: /node_modules/
  10. },
  11. {
  12. test: /.js/,
  13. use: "happypack/loader?id=babel",
  14. include: path.resolve("./src"),
  15. exclude: /node_modules/
  16. }
  17. ]
  18. },
  19. plugins: [
  20. new HtmlWebPackPlugin({
  21. template: "./src/index.html"
  22. }),
  23. new HappyPack({
  24. id: "babel",
  25. loaders: ["babel-loader"]// 和rules里的配置相同
  26. }),
  27. new HappyPack({
  28. id: "css",
  29. loaders: ["style-loader", "css-loader"]// 和rules里的配置相同
  30. }),
  31. ]
四 ParallelUglifyPlugin 1.action

可以把对JS文件的串行压缩变为开启多个子进程并行执行

2 usage

</>复制代码

  1. insatll

</>复制代码

  1. npm install webpack-parallel-uglify-plugin -D

</>复制代码

  1. webpackage.config.js

</>复制代码

  1. new ParallelUglifyPlugin({
  2. workerCount: os.cpus().length - 1,//开启几个子进程去并发的执行压缩。默认是当前运行电脑的 CPU 核数减去1
  3. uglifyJS: {
  4. output: {
  5. beautify: false, //不需要格式化
  6. comments: true, //不保留注释
  7. },
  8. compress: {
  9. warnings: false, // 在UglifyJs删除没有用到的代码时不输出警告
  10. drop_console: true, // 删除所有的 `console` 语句,可以兼容ie浏览器
  11. collapse_vars: true, // 内嵌定义了但是只用到一次的变量
  12. reduce_vars: true, // 提取出出现多次但是没有定义成变量去引用的静态值
  13. }
  14. }
  15. })
五 服务器自动刷新 1 文件监听 1) action

可以监听文件变化,当文件发生变化的时候重新编译

2) useage

</>复制代码

  1. watch: true,
  2. watchOptions: {
  3. ignored: /node_modules/,
  4. aggregateTimeout: 300,
  5. poll: 1
  6. }
3) note

</>复制代码

  1. watch

只有在开启监听模式时(watch为true),watchOptions才有意义

</>复制代码

  1. aggregateTimeout

监听到变化发生后等300(ms)再去执行动作,防止文件更新太快导致编译频率太高

</>复制代码

  1. poll

通过不停的询问文件是否改变来判断文件是否发生变化,默认每秒询问1000次

</>复制代码

  1. 文件监听流程

webpack定时获取文件的更新时间,并跟上次保存的时间进行比对,不一致就表示发生了变化,poll就用来配置每秒问多少次。

当检测文件不再发生变化,会先缓存起来,等等待一段时间后之后再通知监听者,这个等待时间通过aggregateTimeout配置。

webpack只会监听entry依赖的文件
我们需要尽可能减少需要监听的文件数量和检查频率,当然频率的降低会导致灵敏度下降。

2 自动刷新浏览器 1) use

</>复制代码

  1. devServer: {
  2. inline: true
  3. },
2) note

webpack负责监听文件变化,webpack-dev-server负责刷新浏览器。这些文件会被打包到chunk中,它们会代理客户端向服务器发起WebSocket连接

3 模块热替换 1) action

模块热替换(Hot Module Replacement)的技术可在不刷新整个网页的情况下只更新指定的模块.

原理是当一个源码发生变化时,只重新编译发生变化的模块,再用新输出的模块替换掉浏览器中对应的老模块

2) 优点

反应更快,时间更短

不刷新网页可以保留网页运行状态

监听更少的文件

忽略掉 node_modules 目录下的文件

2) use

</>复制代码

  1. webpack.config.js

</>复制代码

  1. devServer: {
  2. hot:true//将hot设置为true
  3. },
  4. // 需要的插件
  5. plugins: [
  6. new webpack.NamedModulesPlugin(),//显示模块的相对路径
  7. new webpack.HotModuleReplacementPlugin()// 启动热加载功能
  8. ]

</>复制代码

  1. code

</>复制代码

  1. if (module.hot) {
  2. module.hot.accept("./hot.js", () => {
  3. let hot = require("./hot");
  4. document.getElementById("app2").innerHTML = hot + "1";
  5. })
  6. }
3 note

</>复制代码

  1. 需要热加载的模块需要在初始化的时候引入到模块中,否则不会触发HMR。
六 区分环境 1 action

在开发网页的时候,一般都会有多套运行环境,例如,在开发过程中方便开发调试的环境。发布到线上给用户使用的运行环境。

线上的环境和开发环境区别主要有以下不同:

线上的代码被压缩

开发环境可能会打印只有开发者才能看到的日志

开发环境和线上环境后端数据接口可能不同

2 usage

</>复制代码

  1. package.json

cross-env跨平台设置环境变量(后面没有&&)

</>复制代码

  1. "scripts": {
  2. "build-dev": "cross-env NODE_ENV=development webpack --mode development",
  3. "build-prod": "cross-env NODE_ENV=production webpack --mode production"
  4. }

</>复制代码

  1. webpack.config.js

根据环境变量区分生产环境还是开发环境,然后和webpack.base.config.js合并,生产环境(或者开发环境)的优先级高于webpack.base.config.js的配置。

</>复制代码

  1. let merge = require("webpack-merge");
  2. let base = require("./webpack.base.config");
  3. let other = null;
  4. if (process.env.NODE_ENV === "development") {
  5. other = require("./webpack.dev.config");
  6. } else {
  7. other = require("./webapack.prod.config");
  8. }
  9. module.exports = merge(base, other);

</>复制代码

  1. webpack.base.config.js

基本配置

webpack.DefinePlugin 定义环境变量

</>复制代码

  1. 基本配置...
  2. plugins: [
  3. new webpack.DefinePlugin({
  4. __isDevelopment__: JSON.stringify(process.env.NODE_ENV == "development")
  5. })
  6. ]

</>复制代码

  1. webpack.dev.config.js

output举例,如果开发和生产环境的参数不同,就会覆盖webpack.base.config.js里面的配置

</>复制代码

  1. const path = require("path");
  2. module.exports = {
  3. output: {
  4. path: path.resolve("./dist"),
  5. filename: "[name].dev.[hash:2].js"
  6. }
  7. };

</>复制代码

  1. webpack.prod.config.js

(以output举例)

</>复制代码

  1. const path = require("path");
  2. module.exports = {
  3. output: {
  4. path: path.resolve("./dist"),
  5. filename: "[name].prod.[hash:8].js"
  6. }
  7. };

</>复制代码

  1. base.js

配置文件中的webpack.DefinePlugin定义的变量(__isDevelopment__),在入口文件和入口文件引用的其他文件中都可以获取到__isDevelopment__的值

</>复制代码

  1. let env = null;
  2. if (__isDevelopment__) {
  3. env = "dev";
  4. } else {
  5. env = "prod";
  6. }
  7. module.exports = env;

</>复制代码

  1. index.js

</>复制代码

  1. let env = require("./base.js");
  2. if (__isDevelopment__) {
  3. console.log("dev");
  4. } else {
  5. console.log("prod");
  6. }
  7. console.log("env", env);
  8. /*
  9. prod
  10. env prod
  11. */
3 note

</>复制代码

  1. webpack.DefinePlugin

定义环境变量的值时用 JSON.stringify 包裹字符串的原因是环境变量的值需要是一个由双引号包裹的字符串,而 JSON.stringify("production")的值正好等于""production""

七 CDN

CDN 又叫内容分发网络,通过把资源部署到世界各地,用户在访问时按照就近原则从离用户最近的服务器获取资源,从而加速资源的获取速度。

HTML文件不缓存,放在自己的服务器上,关闭自己服务器的缓存,静态资源的URL变成指向CDN服务器的地址

静态的JavaScript、CSS、图片等文件开启CDN和缓存,并且文件名带上HASH值

为了并行加载不阻塞,把不同的静态资源分配到不同的CDN服务器上

八 Tree Shaking 1 action

tree Shaking 可以用来剔除JavaScript中用不上的死代码。

2 useage

它依赖静态的ES6模块化语法,例如通过import和export导入导出

不要编译ES6模块

</>复制代码

  1. use: {
  2. loader: "babel-loader",
  3. query: {
  4. presets: [
  5. [
  6. "env", {
  7. modules: false //含义是关闭 Babel 的模块转换功能,保留原本的 ES6 模块化语法
  8. }
  9. ],
  10. "react"
  11. ]
  12. }
  13. },
3 note

需要注意的是它依赖静态的ES6模块化语法,例如通过import和export导入导出。也就是说如果项目代码运行在不支持es6语法的环境上,Tree Shaking也就没有意义了。

九 提取公共代码 1 为什么需要提取公共代码

大网站有多个页面,每个页面由于采用相同技术栈和样式代码,会包含很多公共代码,如果都包含进来会有问题

相同的资源被重复的加载,浪费用户的流量和服务器的成本;
每个页面需要加载的资源太大,导致网页首屏加载缓慢,影响用户体验。 如果能把公共代码抽离成多带带文件进行加载能进行优化,可以减少网络传输流量,降低服务器成本

2 如何提取 1) 分类

不同类型的文件,打包后的代码块也不同:

基础类库,方便长期缓存

页面之间的公用代码

各个页面多带带生成文件

2) usage

</>复制代码

  1. webpack.config.js

</>复制代码

  1. optimization: {
  2. splitChunks: {
  3. cacheGroups: {
  4. commons: {// 页面之间的公用代码
  5. chunks: "initial",
  6. minChunks: 2,
  7. maxInitialRequests: 5, // The default limit is too small to showcase the effect
  8. minSize: 0 // This is example is too small to create commons chunks
  9. },
  10. vendor: {// 基础类库
  11. chunks: "initial",
  12. test: /node_modules/,
  13. name: "vendor",
  14. priority: 10,
  15. enforce: true
  16. }
  17. }
  18. }
  19. },

</>复制代码

  1. ./src/pageA.js

</>复制代码

  1. require("./utils/utility1.js");
  2. require("./utils/utility2.js");
  3. require("react");

</>复制代码

  1. ./src/pageB.js

</>复制代码

  1. require("./utils/utility2.js");
  2. require("./utils/utility3.js");

</>复制代码

  1. ./src/pageC.js

</>复制代码

  1. require("./utils/utility2.js");
  2. require("./utils/utility3.js");

</>复制代码

  1. utils/utility1.js

</>复制代码

  1. module.exports = 1;

</>复制代码

  1. utils/utility2.js

</>复制代码

  1. module.exports = 2;

</>复制代码

  1. utils/utility3.js

</>复制代码

  1. module.exports = 3;

</>复制代码

  1. 打包后的结果

上述三种代码的生成的结果,如下图:

十 Scope Hoisting 1 action

Scope Hoisting 可以让 Webpack 打包出来的代码文件更小、运行的更快, 它又译作 "作用域提升",是在 Webpack3 中新推出的功能。

代码体积更小,因为函数申明语句会产生大量代码

代码在运行时因为创建的函数作用域更少了,内存开销也随之变小 hello.js

2 useage

</>复制代码

  1. package.json

</>复制代码

  1. "build": "webpack --display-optimization-bailout --mode development",

</>复制代码

  1. webpack.config.js

</>复制代码

  1. plugins: [
  2. new ModuleConcatenationPlugin()
  3. ],

</>复制代码

  1. ./h.js

</>复制代码

  1. export default "scope hoist"

</>复制代码

  1. ./index.js

</>复制代码

  1. import str from "./h.js"
  2. console.log(str);
3 note

</>复制代码

  1. 必须使用ES6语法,否则不起作用(--display-optimization-bailout 参数会提示)
十一 代码分离

代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。 有三种常用的代码分离方法:

入口起点:使用 entry 配置手动地分离代码。

防止重复:使用 splitChunks 去重和分离 chunk。

动态导入:通过模块的内联函数调用来分离代码。

入口起点和防止重复上面已经提到了,下面我们重点讲一下动态导入

1 action

用户当前需要用什么功能就只加载这个功能对应的代码,也就是所谓的按需加载 在给单页应用做按需加载优化时,一般采用以下原则:

对网站功能进行划分,每一类一个chunk

对于首次打开页面需要的功能直接加载,尽快展示给用户

某些依赖大量代码的功能点可以按需加载

被分割出去的代码需要一个按需加载的时机

2 usage

使用import(module)的语法

import 异步 加载 模块是一个es7的语法

在webpack里import是一个天然的分割点

</>复制代码

  1. document.getElementById("play").addEventListener("click",function(){
  2. import("./vedio.js").then(function(video){
  3. let name = video.getName();
  4. console.log(name);
  5. });
  6. });
参考文档

webpack官方文档

webpack官方文档中文版

webpackGitHub

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/93904.html

相关文章

  • webpack4.0实战那些事儿

    摘要:刚刚发布,官网自称最大的特点就是零配置。本文就详细介绍一下实战那些事儿。自动刷新监听本地源代码的变化,自动重新构建刷新浏览器。自动发布更新完代码后,自动构建出线上发布代码并传输给发布系统。代码块,一个由多个模块组合而成,用于代码合并与分割。 webpack4.0刚刚发布,官网自称4.0最大的特点就是零配置。本文就详细介绍一下webpack4.0实战那些事儿。 1 什么是WebPack ...

    褰辩话 评论0 收藏0
  • webpack4.0测试版指南

    摘要:我们发布了新版本的插件系统,以便事件挂钩和处理程序是单态的。倒计时按照承诺,我们将从今天开始等待一个月,然后再释放稳定版。这为我们的插件,加载程序和集成生态系统提供了测试,报告和升级到的时间我们需要您帮助我们升级并测试此测试版。 自8月初以来—当我们将 nex branch 合并到webpack/webpack#master—我们看到了巨大的贡献! showImg(https://se...

    MageekChiu 评论0 收藏0
  • 58沈剑用3个小时的视频告诉你高可用的那些事儿

    摘要:本文是到家技术总监沈剑在北京站上的演讲视频。全面解析单点系统的可用性架构与优化消息系统的可达性架构与优化事务系统的一致性架构与优化。时长分钟,建议收藏和转发后在环境下观看。沈剑到家技术总监,互联网架构技术专家,架构师之路公众号作者。 showImg(https://segmentfault.com/img/bVz49R);本文是58到家技术总监沈剑在MPD2016 北京站上的演讲视频。...

    dreamans 评论0 收藏0
  • [聊一聊系列]聊一聊百度移动端首页前端速度那些事儿

    摘要:要快,但是我们的服务也必须万无一失,后续我会分享百度移动端首页的前端架构设计那么这样的优化,是如何做到的呢,又如何兼顾稳定性,架构性,与速度呢别急,让我们把这些优化一一道来。百度移动端首页的很多就是这样缓存在客户端的。 欢迎大家收看聊一聊系列,这一套系列文章,可以帮助前端工程师们了解前端的方方面面(不仅仅是代码):https://segmentfault.com/blog/fronte...

    The question 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<