webpack相关

webpack的理解,解决了什么问题?

webpack 是一个用于现代JavaScript应用程序的静态模块打包工具

webpack可以解决一下几个问题:

  • 需要通过模块化的方式来开发
  • 使用一些高级特性来提高开发效率,比如通过ES6+、TS开发脚本逻辑,scss、less等CSS扩展语言来编写css
  • 监听文件的变化来并且反映到浏览器上,提高开发的效率(热更新)
  • 开发完毕后需要对代码进行压缩、合并以及其他相关优化

webpack的构建流程

webpack的运行流程是个串行的过程,它的工作流程就是将各个插件串联起来

从启动到结束会依次执行一下三个步骤:

  • 初始化流程:从配置文件或shell语句中读取或合并参数,并初始化需要使用的插件和配置插件等执行环境所需要的参数

  • 编辑构建流程:从Entry出发,针对每个Module串行调用对应的Loader去翻译文件内容,再找到该Module依赖的Module,递归地进行编译处理

    初始化后会调用Compiler的run来真正启动webpack编译构建流程:

    • complie 开始编译(构建一个compilation 对象,执行模块创建、依赖收集、分块、打包等主要任务)
    • make 从入口点文件分析模块及其依赖的模块,创建这些模块对象
    • build-module 构建模块(调用配置的loaders,模块转换完后,使用acorn解析输出AST树)
    • seal 封装构建结果
    • emit 把各个chunk输出到结果文件
  • 输出流程:对编译后的 Module 组合成 Chunk,把 Chunk 转换成文件,输出到文件系统

webpack 中常见的Loader

loader用于对模块的源码进行转换,在import或者加载模块时预处理文件。

像一些css、sass、png等文件,webpack则无能为力,这个时候就需要配置对应的loader进行文件内容的解析

配置loader的三种方式:

  • 配置方式(推荐),在webpack.config.js中指定loader

    在module.rules属性中配置,rules是一个数组,所以可以配置多个loader,每个配置是一个对象,test属性表示匹配规则,一般是正则表达式,use属性来调用对应的loader

  • 内联方式:在每个import语句中显式指定loader

  • cli方式:在shell命令中指定它们

特性:

  • loader可以同步也可以异步
  • loader运行在node.js中,并且可以执行任何操作
  • 除了常见的通过 package.jsonmain 来将一个 npm 模块导出为 loader,还可以在 module.rules 中使用 loader 字段直接引用一个模块
  • 插件(plugin)可以为 loader 带来更多特性
  • loader 能够产生额外的任意文件

常见的loader:

  • css-loader

    分析css模块之间的关系,合并成一个css。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    npm install --save-dev css-loader // sh

    rules: [
    ...,
    {
    test: /\.css$/,
    use: {
    loader: "css-loader",
    options: {
    // 启用/禁用 url() 处理
    url: true,
    // 启用/禁用 @import 处理
    import: true,
    // 启用/禁用 Sourcemap
    sourceMap: false
    }
    }
    }
    ]

    如果只通过css-loader加载文件,这时候页面代码设置的样式并没有生效。

    原因在于,css-loader只是负责将.css文件进行一个解析,而并不会将解析后的css插入到页面中

    如果我们希望再完成插入style的操作,那么我们还需要另外一个loader,就是style-loader

  • style-loader

    把css-loader生成的内容,用style标签挂载到页面的head中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    npm install --save-dev style-loader // sh

    rules: [
    ...,
    {
    test: /\.css$/,
    use: ["style-loader", "css-loader"]
    }
    ]
  • less-loader/sass-loader

    开发中,我们也常常会使用lesssassstylus预处理器编写css样式,使开发效率提高

  • raw-loader

    webpack中通过 import方式导入文件内容,该loader并不是内置的,所以首先要安装

  • file-loader

    把识别出的资源模块,移动到指定的输出⽬目录,并且返回这个资源在输出目录的地址(字符串)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    npm install --save-dev file-loader // sh


    rules: [
    ...,
    {
    test: /\.(png|jpe?g|gif)$/,
    use: {
    loader: "file-loader",
    options: {
    // placeholder 占位符 [name] 源资源模块的名称
    // [ext] 源资源模块的后缀
    name: "[name]_[hash].[ext]",
    //打包后的存放位置
    outputPath: "./images",
    // 打包后文件的 url
    publicPath: './images',
    }
    }
    }
    ]
  • url-loader

    可以处理理 file-loader 所有的事情,但是遇到图片格式的模块,可以选择性的把图片转成 base64 格式的字符串,并打包到 js 中,对小体积的图片比较合适,大图片不合适。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    npm install --save-dev url-loader // sh

    rules: [
    ...,
    {
    test: /\.(png|jpe?g|gif)$/,
    use: {
    loader: "url-loader",
    options: {
    // placeholder 占位符 [name] 源资源模块的名称
    // [ext] 源资源模块的后缀
    name: "[name]_[hash].[ext]",
    //打包后的存放位置
    outputPath: "./images"
    // 打包后文件的 url
    publicPath: './images',
    // 小于 100 字节转成 base64 格式
    limit: 100
    }
    }
    }
    ]

webpack 中常见的plugin, 解决了什么问题?

plugin赋予其各种灵活的功能,运行在 webpack 的不同阶段(钩子 / 生命周期),贯穿了webpack整个编译周期,目的在于解决loader 无法实现的其他事

特性:

本质是一个具有apply方法的js对象,apply方法会被webpack compiler调用,并且在整个编译生命周期都可以访问到compiler对象

1
2
3
4
5
6
7
8
9
10
11
const pluginName = 'ConsoleLogOnBuildWebpackPlugin';

class ConsoleLogOnBuildWebpackPlugin {
apply(compiler) {
compiler.hooks.run.tap(pluginName, (compilation) => {
console.log('webpack 构建过程开始!');
});
}
}

module.exports = ConsoleLogOnBuildWebpackPlugin;

编译生命周期钩子,如下:

  • entry-option: 初始化option
  • run
  • compile: 真正开始编译,在创建compilation对象之前
  • Compilation:生成了compilation对象
  • make:从entry开始递归分析依赖,准备对每个模块进行build
  • after-compile: 编译build过程结束
  • emit: 在将内存中 assets 内容写到磁盘文件夹之前
  • after-emit: 在将内存中 assets 内容写到磁盘文件夹之后
  • done: 完成所有的编译过程
  • failed:编译失败的时候

常见的plugin

  • html-webpack-plugin

    在打包结束后,⾃动生成⼀个 html ⽂文件,并把打包生成的js 模块引⼊到该 html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // webpack.config.js
    const HtmlWebpackPlugin = require("html-webpack-plugin");
    module.exports = {
    ...
    plugins: [
    new HtmlWebpackPlugin({
    title: "My App",
    filename: "app.html",
    template: "./src/html/index.html"
    })
    ]
    };
  • clean-webpack-plugin(删除(清理)构建目录)

  • mini-css-extract-plugin(提取css到一个独立的文件中)

  • DefinePlugin

    允许在编译时创建配置的全局对象,是一个webpack内置的插件,不需要安装

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const { DefinePlugun } = require('webpack')

    module.exports = {
    ...
    plugins:[
    new DefinePlugin({
    BASE_URL:'"./"'
    })
    ]
    }

    这时候编译template模块的时候,就能通过下述形式获取全局对象

    1
    <link rel="icon" href="<%= BASE_URL%>favicon.ico>"
  • copy-webpack-plugin

    复制文件或目录到执行区域,如vue的打包过程中,如果我们将一些文件放到public的目录下,那么这个目录会被复制到dist文件夹中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    new CopyWebpackPlugin({
    parrerns:[
    {
    from:"public",
    globOptions:{
    ignore:[
    '**/index.html'
    ]
    }
    }
    ]
    })

    复制的规则在patterns属性中设置:

    • from:设置从哪一个源中开始复制

    • to:复制到的位置,可以省略,会默认复制到打包的目录下

    • globOptions:设置一些额外的选项,其中可以编写需要忽略的文件

说说Loader和plugin的区别?编写Loader和plugin的思路?

概念:

  • loader是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中
  • plugin 赋予了 webpack 各种灵活的功能,例如打包优化、资源管理、环境变量注入等,目的是解决 loader 无法实现的其他事

运行时机的区别:

  • loader是运行在打包文件之前
  • plugin在整个编译周期都起作用

在webpack运行的生命周期中会广播出许多事件,plugin可以监听这些事件,在合适的时机通过webpack提供的api改变输出结果。

对于loader实质是一个转化器,将A文件进行编译转化为B文件,操作的是文件。

编写loader:

loader本质是一个函数,函数中的 this 作为上下文会被 webpack 填充,因此我们不能将 loader设为一个箭头函数,

函数接受一个参数,为 webpack 传递给 loader 的文件源内容,函数中 this 是由 webpack 提供的对象,能够获取当前 loader 所需要的各种信息,函数中有异步操作或同步操作,异步操作通过 this.callback 返回,返回值要求为 string 或者 Buffer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 导出一个函数,source为webpack传递给loader的文件源内容
module.exports = function(source) {
const content = doSomeThing2JsString(source);

// 如果 loader 配置了 options 对象,那么this.query将指向 options
const options = this.query;

// 可以用作解析其他模块路径的上下文
console.log('this.context');

/*
* this.callback 参数:
* error:Error | null,当 loader 出错时向外抛出一个 error
* content:String | Buffer,经过 loader 编译后需要导出的内容
* sourceMap:为方便调试生成的编译后内容的 source map
* ast:本次编译生成的 AST 静态语法树,之后执行的 loader 可以直接使用这个 AST,进而省去重复生成 AST 的过程
*/
this.callback(null, content); // 异步
return content; // 同步
}

一般在编写loader的过程中,保持功能单一,避免做多种功能

编写plugin:

由于webpack基于发布订阅模式,在运行的生命周期中会广播出许多事件,插件通过监听这些事件,就可以在特定的阶段执行自己的插件任务

webpack编译会创建两个核心对象:

  • compiler:包含了 webpack 环境的所有的配置信息,包括 options,loader 和 plugin,和 webpack 整个生命周期相关的钩子

  • compilation:作为 plugin 内置事件回调函数的参数,包含了当前的模块资源、编译生成资源、变化的文件以及被跟踪依赖的状态信息。当检测到一个文件变化,一次新的 Compilation 将被创建

    如果自己要实现plugin,也需要遵循一定的规范:

    • 插件必须是一个函数或者是一个包含 apply 方法的对象,这样才能访问compiler实例
    • 传给每个插件的 compilercompilation 对象都是同一个引用,因此不建议修改
    • 异步的事件需要在插件处理完任务时调用回调函数通知 Webpack 进入下一个流程,不然会卡住
1
2
3
4
5
6
7
8
9
10
11
12
class MyPlugin {
// Webpack 会调用 MyPlugin 实例的 apply 方法给插件实例传入 compiler 对象
apply (compiler) {
// 找到合适的事件钩子,实现自己的插件功能
compiler.hooks.emit.tap('MyPlugin', compilation => {
// compilation: 当前打包构建流程的上下文
console.log(compilation);

// do something...
})
}
}

emit 事件发生时,代表源文件的转换和组装已经完成,可以读取到最终将输出的资源、代码块、模块及其依赖,并且可以修改输出资源的内容

webpack的热更新是如何做到的?

总结:

  • 通过webpack-dev-server创建两个服务器:提供静态资源服务(express)和Socket服务
  • express server 负责直接提供静态资源服务(打包后的资源直接被浏览器请求和解析)
  • socket server是一个websocket的长链接,可双向进行通信
  • 当socket server 监听到对应模块发生变化时,会生产两个文件,.json(manifest) 、.js文件(update chunk)
  • 通过长链接,socket server可直接将这两个文件主动发送给客户端(浏览器)
  • 浏览器拿到两个新的文件后,通过HMR runtime机制,加载这两个文件,并且针对修改的模块进行更新

webpack proxy 的工作原理,为什么能解决跨域?

Webpack proxy 是webpack提供的代理服务,可接收客户端请求进行转发到对应的服务器,解决前端开发模式下跨域的问题

通过服务器工具webpack-dev-server实现,只适用在开发阶段

关于配置方面,在webpack配置对象属性中通过devServer属性提供,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ./webpack.config.js
const path = require('path')

module.exports = {
// ...
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 9000,
proxy: {
'/api': {
target: 'https://api.github.com'
}
}
// ...
}
}

devServetr里面proxy则是关于代理的配置,该属性为对象的形式,对象中每一个属性就是一个代理的规则匹配

属性的名称是需要被代理的请求路径前缀,一般为了辨别都会设置前缀为/api,值为对应的代理匹配规则,对应如下:

pathRewrite:默认情况下,我们的 /api-hy 也会被写入到URL中,如果希望删除,可以使用pathRewrite

  • secure:默认情况下不接收转发到https的服务器上,如果希望支持,可以设置为false
  • changeOrigin:它表示是否更新代理后请求的 headers 中host地址

工作原理:

proxy的工作原理实质上是利用http-proxy-middleware 这个http代理中间件,实现请求转发给其他服务器

在开发阶段,本地地址为http://localhost:3000,该浏览器发送一个前缀带有/api标识的请求到服务端获取数据,但响应这个请求的服务器只是将请求转发到另一台服务器中

1
2
3
4
5
const express = require('express')
const proxy = require('http-proxy-middleware')
const app = express()
app.use('api', proxy({ target: 'http://www.example.org', changeOrigin: true }))
app.listen(3000)

跨域:

本地开发时候,通过webpack-dev-server启动了一个服务独立运行在localhost的一个端口上,而后端又是运行在另外一台服务器中,由于浏览器的同源策略的原因,因此本地访问会出现跨域的问题。

通过设置webpack proxy实现代理请求后,相当于浏览器与服务端中添加一个代理者

当本地发送请求的时候,代理服务器响应该请求,并将请求转发到目标服务器,目标服务器响应数据后再将数据返回给代理服务器,最终再由代理服务器将数据响应给本地。

服务器与服务器之间请求数据并不会存在跨域行为,跨域行为是浏览器安全策略限制

如何借助webpack来优化前端性能?

手段:

  • JS代码压缩(terser-webpack-plugin)

    terser是一个JavaScript的解释、绞肉机、压缩机的工具集,可以帮助我们压缩、丑化我们的代码,让bundle更小

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    const TerserPlugin = require('terser-webpack-plugin')
    module.exports = {
    ...
    optimization: {
    minimize: true,
    minimizer: [
    new TerserPlugin({
    parallel: true // 电脑cpu核数-1
    })
    ]
    }
    }
    • extractComments:默认值为true,表示会将注释抽取到一个单独的文件中,开发阶段,我们可设置为 false ,不保留注释
    • parallel:使用多进程并发运行提高构建的速度,默认值是true,并发运行的默认数量: os.cpus().length - 1
    • terserOptions:设置我们的terser相关的配置:
    • compress:设置压缩相关的选项,mangle:设置丑化相关的选项,可以直接设置为true
    • mangle:设置丑化相关的选项,可以直接设置为true
    • toplevel:底层变量是否进行转换
    • keep_classnames:保留类的名称
    • keep_fnames:保留函数的名称
  • css代码压缩(css-minimizer-webpack-plugin)

    css压缩通常是去除无用的空格等

  • HTML代码压缩(HtmlWebpackPlugin)

    使用HtmlWebpackPlugin插件来生成HTML的模板时候,通过配置属性minify进行html优化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    module.exports = {
    ...
    plugin:[
    new HtmlwebpackPlugin({
    ...
    minify:{
    minifyCSS:false, // 是否压缩css
    collapseWhitespace:false, // 是否折叠空格
    removeComments:true // 是否移除注释
    }
    })
    ]
    }
  • 文件大小压缩(compression-webpack-plugin)

    对文件的大小进行压缩,减少http传输过程中宽带的损耗

    1
    2
    3
    4
    5
    6
    new ComepressionPlugin({
    test:/\.(css|js)$/, // 哪些文件需要压缩
    threshold:500, // 设置文件多大开始压缩
    minRatio:0.7, // 至少压缩的比例
    algorithm:"gzip", // 采用的压缩算法
    })
  • 图片压缩(file-loader、image-webpack-loader)

    一般来说在打包之后,一些图片文件的大小是远远要比 js 或者 css 文件要来的大,所以图片压缩较为重要

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    module: {
    rules: [
    {
    test: /\.(png|jpg|gif)$/,
    use: [
    {
    loader: 'file-loader',
    options: {
    name: '[name]_[hash].[ext]',
    outputPath: 'images/',
    }
    },
    {
    loader: 'image-webpack-loader',
    options: {
    // 压缩 jpeg 的配置
    mozjpeg: {
    progressive: true,
    quality: 65
    },
    // 使用 imagemin**-optipng 压缩 png,enable: false 为关闭
    optipng: {
    enabled: false,
    },
    // 使用 imagemin-pngquant 压缩 png
    pngquant: {
    quality: '65-90',
    speed: 4
    },
    // 压缩 gif 的配置
    gifsicle: {
    interlaced: false,
    },
    // 开启 webp,会把 jpg 和 png 图片压缩为 webp 格式
    webp: {
    quality: 75
    }
    }
    }
    ]
    },
    ]
    }
  • tree sharking

    Tree Shaking 是一个术语,在计算机中表示消除死代码,依赖于ES Module的静态语法分析(不执行任何的代码,可以明确知道模块的依赖关系)

    两种方案:

    • usedExports: 通过标记某些函数是否被使用,之后通过Terser来进行优化的
    • sideEffects:跳过整个模块/文件,直接查看该文件是否有副作用

    usedExports:

    配置方法也很简单,只需要将usedExports设为true

    1
    2
    3
    4
    5
    6
    module.exports = {
    ...
    optimization:{
    usedExports
    }
    }

    使用之后,没被用上的代码在webpack打包中会加入unused harmony export mul注释,用来告知 Terser 在优化时,可以删除掉这段代码

    sideEffects:

    sideEffects用于告知webpack compiler哪些模块时有副作用,配置方法是在package.json中设置sideEffects属性

    如果sideEffects设置为false,就是告知webpack可以安全的删除未用到的exports

    1
    2
    3
    4
    "sideEffecis":[
    "./src/util/format.js",
    "*.css" // 所有的css文件
    ]

    css tree shaking

    css进行tree shaking优化可以安装PurgeCss插件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const PurgeCssPlugin = require('purgecss-webpack-plugin')
    module.exports = {
    ...
    plugins:[
    new PurgeCssPlugin({
    path:glob.sync(`${path.resolve('./src')}/**/*`), {nodir:true}// src里面的所有文件
    satelist:function(){
    return {
    standard:["html"]
    }
    }
    })
    ]
    }
    • paths:表示要检测哪些目录下的内容需要被分析,配合使用glob
    • 默认情况下,Purgecss会将我们的html标签的样式移除掉,如果我们希望保留,可以添加一个safelist的属性
  • 代码分离

    将代码分离到不同的bundle中,之后我们可以按需加载,或者并行加载这些文件

    默认情况下,所有的JavaScript代码(业务代码、第三方依赖、暂时没有用到的模块)在首页全部都加载,就会影响首页的加载速度

    代码分离可以分出出更小的bundle,以及控制资源加载优先级,提供代码的加载性能

    这里通过splitChunksPlugin来实现,该插件webpack已经默认安装和集成,只需要配置即可

    默认配置中,chunks仅仅针对于异步(async)请求,我们可以设置为initial或者all

    1
    2
    3
    4
    5
    optimization:{
    splitChunks:{
    chunks:"all"
    }
    }

    splitChunks主要属性有如下:

    • Chunks,对同步代码还是异步代码进行处理
    • minSize: 拆分包的大小, 至少为minSize,如何包的大小不超过minSize,这个包不会拆分
    • maxSize: 将大于maxSize的包,拆分为不小于minSize的包
    • minChunks:被引入的次数,默认是1
  • 内联chunk

    可以通过InlineChunkHtmlPlugin插件将一些chunk的模块内联到html,如runtime的代码(对模块进行解析、加载、模块信息相关的代码),代码量并不大,但是必须加载的

关于webpack对前端性能的优化,可以通过文件体积大小入手,其次还可通过分包的形式、减少http请求次数等方式,实现对前端性能的优化

如何提高webpack的构建速度

优化手段:

  • 优化 loader 配置

    在使用loader时,可以通过配置includeexcludetest属性来匹配文件,接触includeexclude规定哪些匹配应用loader

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    module.exports = {
    module: {
    rules: [
    {
    // 如果项目源码中只有 js 文件就不要写成 /\.jsx?$/,提升正则表达式性能
    test: /\.js$/,
    // babel-loader 支持缓存转换出的结果,通过 cacheDirectory 选项开启
    use: ['babel-loader?cacheDirectory'],
    // 只对项目根目录下的 src 目录中的文件采用 babel-loader
    include: path.resolve(__dirname, 'src'),
    },
    ]
    },
    };
  • 合理使用 resolve.extensions

    在开发中我们会有各种各样的模块依赖,这些模块可能来自于自己编写的代码,也可能来自第三方库, resolve可以帮助webpack从每个 require/import 语句中,找到需要引入到合适的模块代码

    通过resolve.extensions是解析到文件时自动添加拓展名,默认情况如下

    1
    2
    3
    4
    module.exports = {
    ...
    extensions:[".warm",".mjs",".js",".json"]
    }

    当我们引入文件的时候,若没有文件后缀名,则会根据数组内的值依次查找

    当我们配置的时候,则不要随便把所有后缀都写在里面,这会调用多次文件的查找,这样就会减慢打包速度

  • 优化 resolve.modules

    resolve.modules 用于配置 webpack 去哪些目录下寻找第三方模块。默认值为['node_modules'],所以默认会从node_modules中查找文件 当安装的第三方模块都放在项目根目录下的 ./node_modules目录下时,所以可以指明存放第三方模块的绝对路径,以减少寻找,配置如下:

    1
    2
    3
    4
    5
    6
    7
    module.exports = {
    resolve: {
    // 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤
    // 其中 __dirname 表示当前工作目录,也就是项目根目录
    modules: [path.resolve(__dirname, 'node_modules')]
    },
    };
  • 优化 resolve.alias

    alias给一些常用的路径起一个别名,特别当我们的项目目录结构比较深的时候,一个文件的路径可能是./../../的形式

    通过配置alias以减少查找过程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    module.exports = {
    ...
    resolve:{
    alias:{
    "@":path.resolve(__dirname,'./src')
    }
    }
    }
    #
  • 使用 DLLPlugin 插件

    DLL全称是 动态链接库,是为软件在winodw种实现共享函数库的一种实现方式,而Webpack也内置了DLL的功能,为的就是可以共享,不经常改变的代码,抽成一个共享的库。这个库在之后的编译过程中,会被引入到其他项目的代码中

    使用步骤分成两部分:

    • 打包一个 DLL 库
    • 引入 DLL 库
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 打包一个 DLL 库,webpack内置了一个DllPlugin可以帮助我们打包一个DLL的库文件
    module.exports = {
    ...
    plugins:[
    new webpack.DllPlugin({
    name:'dll_[name]',
    path:path.resolve(__dirname,"./dll/[name].manifest.json")
    })
    ]
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 使用 webpack 自带的 DllReferencePlugin 插件对 mainfest.json 映射文件进行分析,获取要使用的DLL库
    // 然后再通过AddAssetHtmlPlugin插件,将我们打包的DLL库引入到Html模块中
    module.exports = {
    ...
    new webpack.DllReferencePlugin({
    context:path.resolve(__dirname,"./dll/dll_react.js"),
    mainfest:path.resolve(__dirname,"./dll/react.mainfest.json")
    }),
    new AddAssetHtmlPlugin({
    outputPath:"./auto",
    filepath:path.resolve(__dirname,"./dll/dll_react.js")
    })
    }
  • 使用 cache-loader

    在一些性能开销较大的 loader之前添加 cache-loader,以将结果缓存到磁盘里,显著提升二次构建速度

    保存和读取这些缓存文件会有一些时间开销,所以请只对性能开销较大的 loader 使用此loader

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    module.exports = {
    module: {
    rules: [
    {
    test: /\.ext$/,
    use: ['cache-loader', ...loaders],
    include: path.resolve('src'),
    },
    ],
    },
    };
  • terser 启动多线程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //使用多进程并行运行来提高构建速度
    module.exports = {
    optimization: {
    minimizer: [
    new TerserPlugin({
    parallel: true,
    }),
    ],
    },
    };
  • 合理使用 sourceMap(打包生成 sourceMap 的时候,如果信息越详细,打包速度就会越慢)

总结: 可以看到,优化webpack构建的方式有很多,主要可以从优化搜索时间、缩小文件搜索范围、减少不必要的编译等方面入手

与webpack类似的打包工具有哪些?区别?

  • Rollup

    Rollup 是一款 ES Modules 打包器,从作用上来看,RollupWebpack 非常类似。不过相比于 WebpackRollup要小巧的多

    Rollup的优点:

    • 代码更简洁、效率更高
    • 默认支持 Tree-shaking

    但缺点也十分明显,加载其他类型的资源文件或者支持导入 CommonJS 模块,又或是编译 ES 新特性,这些额外的需求 Rollup需要使用插件去完成

    综合来看,rollup并不适合开发应用使用,因为需要使用第三方模块,而目前第三方模块大多数使用CommonJs方式导出成员,并且rollup不支持HMR,使开发效率降低

    但是在用于打包JavaScript 库时,rollupwebpack 更有优势,因为其打包出来的代码更小、更快,其存在的缺点可以忽略

  • Parcel

    Parcel ,是一款完全零配置的前端打包器,它提供了 “傻瓜式” 的使用体验,只需了解简单的命令,就能构建前端应用程序

    执行命令后,Parcel不仅打包了应用,同时也启动了一个开发服务器,跟webpack Dev Server一样

    webpack类似,也支持模块热替换,但用法更简单

    同时,Parcel有个十分好用的功能:支持自动安装依赖,像webpack开发阶段突然使用安装某个第三方依赖,必然会终止dev server然后安装再启动。而Parcel则免了这繁琐的工作流程

    同时,Parcel能够零配置加载其他类型的资源文件,无须像webpack那样配置对应的loader

  • Snowpack

    Snowpack,是一种闪电般快速的前端构建工具,专为现代Web设计,较复杂的打包工具(如WebpackParcel)的替代方案,利用JavaScript的本机模块系统,避免不必要的工作并保持流畅的开发体验

    开发阶段,每次保存单个文件时,WebpackParcel都需要重新构建和重新打包应用程序的整个bundle。而Snowpack为你的应用程序每个文件构建一次,就可以永久缓存,文件更改时,Snowpack会重新构建该单个文件

  • Vite

    vite ,是一种新型前端构建工具,能够显著提升前端开发体验

    它主要由两部分组成:

    • 一个开发服务器,它基于 原生 ES 模块 提供了丰富的内建功能,如速度快到惊人的 模块热更新HMR
    • 一套构建指令,它使用 Rollup打包你的代码,并且它是预配置的,可以输出用于生产环境的优化过的静态资源

    其作用类似webpack+ webpack-dev-server,其特点如下:

    • 快速的冷启动
    • 即时的模块热更新
    • 真正的按需编译

    vite会直接启动开发服务器,不需要进行打包操作,也就意味着不需要分析模块的依赖、不需要编译,因此启动速度非常快

    利用现代浏览器支持ES Module的特性,当浏览器请求某个模块的时候,再根据需要对模块的内容进行编译,这种方式大大缩短了编译时间

    在热模块HMR方面,当修改一个模块的时候,仅需让浏览器重新请求该模块即可,无须像webpack那样需要把该模块的相关依赖模块全部编译一次,效率更高


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!