Webpack5基础

前言

前端开发时一般都会使用框架(React、Vue)、ES6模块化语法、Less或者Sass等 css预处现器等语法。这样的代码要想在浏览器运行,必须要编译成浏览器能够识别的 JS、Css等语法。而打包工具的作用就是压缩代码、兼容性处理、提升代码性能、代码编译等。

常见的打包工具包括Grunt、Gulp、Parcel、Webpack、Rollup、Vite等等,目前市面上最常用的是webpack。

第一章 Webpack的基本配置

1. 基本使用

Webpack 是一个静态资源打包工具。它会以一个或多个文件作为打包的入口,将我们整个项目所有文件编译组合成一个或多个文件输出。输出的文件就是编译好的文件bundle,可以在浏览器端运行。

Webpack本身的功能是有限的:

  • 开发模式:只能编译JS中的ES Module语法
  • 生产模式:不仅可以编译JS中的ES Module语法,还可以压缩JS代码
  1. 直接使用JS代码

index.html主文件
Webpack5基础

未打包的main.js文件
Webpack5基础

控制台报错
Webpack5基础

  1. 使用打包后的JS代码

第一步:初始化一个package.json文件npm init -y

Webpack5基础

第二步:下载webpack npm i webpack webpack-cli -D

第三步:打包指定目录文件npx webpack ./src/main.js --mode=development

development模式
Webpack5基础

production模式
Webpack5基础

控制台正常输出
Webpack5基础

2. 核心概念

  1. Entry:入口文件,webpack编译的起点,即从哪个文件开始打包
  2. output:输出,webpack打包完的文件从哪里输出。其中output.filename对应initial chunk文件名称,output.chunkFilename对应non-initial chunk文件名称
  3. Loader:加载器,webpack本身只能处理JS、json资源文件,其他资源需要借助loader处理
  4. Plugin:插件,扩展webpack功能
  5. mode:模式,分为development开发模式和production生产模式

3. 其他概念

  1. Compiler:编译管理器,webpack启动后会创建compiler对象,该对象一直存活到编译结束
  2. Compilation:单次编译过程的管理器,每次触发重新编译时,都会创建一个新的compilation对象
  3. Dependence:依赖对象,webpack基于该类型记录模块间依赖关系
  4. Module:webpack内部所有资源都会以“module”对象形式存在,所有关于资源的操作、转译、合并都是以 “module” 为基本单位进行的
  5. Chunk:编译完成准备输出时,webpack会将module按特定的规则组织成一个一个的chunk,这些chunk某种程度上跟最终输出一一对应

4. Webpack基本配置

在根目录下创建一个Webpack.config.js文件并完成基础配置

const path = require("path");

module.exports = {
    // 入口,相对路径
    entry: "./src/main.js",
    // 输出,绝对路径
    output: {
        path: path.resolve(__dirname,"dist"), // 路径
        filename: "main.js",// 文件名
    },
    // 加载器
    module: {rules: []},
    // 插件
    plugins:[],
    // 模式
    mode: "development"
}

可以在output中添加clean配置,自动清除上一次的打包资源。

output: {
    path: path.resolve(__dirname,"dist"), // 路径
    filename: "main.js",// 文件名
    clean: true, // 在生成文件之前清空output目录
},

第二章 资源文件的处理

1. 处理样式资源

1.1 style-loader

作用:把CSS插入到DOM中,推荐将style-loadercss-loader一起使用

// 下载
npm install --save-dev style-loader

// 使用加载器
module: {
    rules: [
        {
            test: /\.css$/i, // 正则表达式匹配文件
            use: ["style-loader", "css-loader"], // 从右到左依次使用loader处理
        }
    ]
},

1.2 css-loader

css-loader会对@importurl()进行处理,就像js解析import/require()一样。

// 下载
npm install --save-dev css-loader

// 使用加载器
module: {
    rules: [
        {
            test: /\.css$/i, // 正则表达式匹配文件
            use: ["style-loader", "css-loader"], // 从右到左依次使用loader处理
        }
    ]
},

Webpack5基础

1.3 less-loader

作用:将Less编译为CSS的loader

// 下载 
npm install less less-loader --save-dev

// 使用加载器
module: {
    rules: [
        {
            test: /\.less$/i,
            use: ['style-loader','css-loader','less-loader'],
        },
    ]
},

Webpack5基础

1.4 sass-loader

作用:加载Sass/SCSS文件并将他们编译为CSS。

// 下载 
npm install sass-loader sass --save-dev

// 使用加载器
module: {
    rules: [
        {
            test: /\.s[ac]ss$/i,
            use: [
                // 将JS字符串生成为style节点
                'style-loader',
                // 将CSS转化成CommonJS模块
                'css-loader',
                // 将Sass编译成CSS
                'sass-loader',
            ],
        },
    ]
},

Webpack5基础

1.5 stylus-loader

作用:将Stylus文件编译为CSS

// 下载 
npm install stylus stylus-loader --save-dev

// 使用加载器
module: {
    rules: [
        {
            test: /.styl$/,
            loader: "stylus-loader",
        },
    ]
},

2. 处理图片资源

2.1 简单配置

Webpack4处理图片资源时需要使用file-loader和url-loader两个加载器,而Webpack5已经内置了两个Loader,使用时不需要单独下载安装,只需要简单配置即可。

module: {
    rules: [
        {
            test: /\.(png|jpe?g|gif|webp)$/i,
            type: "asset",
        }
    ]
},

图片作为背景图片,通过url引入
Webpack5基础

Webpack5基础

2.2 资源模块

资源模块(asset module)是一种模块类型,它允许使用资源文件(字体,图标等)而无需配置额外loader。包括以下内容:

  • raw-loader:将文件导入为字符串
  • url-loader:将文件作为data URI内联到bundle中
  • file-loader:将文件发送到输出目录

一般情况下webpack将按照默认条件,自动地在resource和inline之间进行选择:小于8kb的文件,将会视为inline模块类型,否则会被视为resource模块类型。可以通过在webpack配置的module rule层级中,设置Rule.parser.dataUrlCondition.maxSize选项来修改此条件。

  • resource资源:直接发送到输出目录,其路径会被被注入到bundle中
  • inline资源:文件会作为data URI注入到bundle中,格式为base64,可以减少网络请求
module: {
    rules: [
        {
            test: /\.(png|jpe?g|gif|webp)$/i,
            type: "asset",
            parser: {
                dataUrlCondition: {
                    maxSize: 100 * 1024 // 小于100kb转化为inline资源
                }
            }
        }
    ]
},

Webpack5基础

🤔:url-loader和file-loader的区别是什么?

🙋:大致总结如下

首先概念不同:file-loader可以指定要复制和放置资源文件的位置,以及如何使用版本哈希命名以获得更好的缓存。url-loader允许你有条件地将文件转换为内联的base-64 URL (当文件小于给定的阈值),这会减少小文件的HTTP请求数。如果文件大于该阈值,会自动的交给file-loader处理。

处理图片资源方式不同:file-loader将文件上的importrequire解析为url,并将该文件发射到输出目录中。url-loader可以识别图片的大小,并把图片转换成base64,从而减少代码的体积,如果图片超过设定的限制,就会继续用file-loader处理。

2.3 自定义输出文件

默认情况下,asset/resource模块以[hash][ext][query]文件名发送到输出目录中,可以通过在webpack配置中设置output.assetModuleFilename来修改此模板字符串。

特点:不能对asset/resource模块下的内容进行区分

output: {
    // 所有输出文件的路径
    path: path.resolve(__dirname,"dist"),
    // 入口文件对应的输出文件名称
    filename: "main.js",
    // asset/resource模块的输出路径和名称配置
    assetModuleFilename: 'images/[hash][ext][query]'
},

也可以通过generator.filename单独设置某个resource模块,输出到指定目录下。

{
    test: /\.(png|jpe?g|gif|webp)$/i,
    type: "asset",
    parser: {
        dataUrlCondition: {
            maxSize: 100 * 1024 // 100kb
        }
    },
    // 将图片资源输出到dist/static/images目录中
    // 文件名为前8位hash值 + 文件扩展名 + 其他扩展字段
    generator: {
        filename: 'static/images/[hash:8][ext][query]'
    }
}

Webpack5基础

3. 处理字体图标资源

字体图标资源也属于资源模块,但是不需要转化为base-64格式,所以需要使用Resource。

{
    test: /\.(ttf|Woff2?)$/i,
    type:"asset/resource",
    generator: {
        filename: 'static/media/[hash:8][ext][query]'
    }
}

4. 处理其他资源

例如音频、视频等标资源也属于资源模块,同样也不需要转化为base-64格式,所以需要使用Resource。

{
    test: /\.(map3|map4|avi)$/i,
    type:"asset/resource",
    generator: {
        filename: 'static/media/[hash:8][ext][query]'
    }
}

5. 处理JS资源

Webpack对JS的处理是有限的,只能编译JS中ES模块化语法,不能编译其他语法,导致JS不能在IE等浏览器中运行,所以需要做一些兼容性处理。

  • Babel:JS兼容性处理
  • Eslint:代码格式校验

需要先完成Eslint检测代码格式无误后,再由Babel做代码兼容性处理。

5.1 Eslint

作用:用来检测js和jsx语法的工具,可以扩展各种功能

  1. 配置文件
  • .eslintrc.*:新建位于项目根目录的文件,可以是.js或者.json格式
  • package.json中eslintConfig:直接在package文件中添加配置,Eslint会自动查找和读取对应配置规则
  1. 使用
// 下载
npm install eslint-webpack-plugin eslint --save-dev
// 添加配置文件.eslintrc.js
module.exports = {
    // 继承eslint规则
    extends:["eslint:recommended"],
    env:{
        node:true, // 启用node中的全局变量
        browser:true, // 启用浏览器中的全局变量
    },
    parserOptions:{
        ecmaVersion: 6,
        sourceType: "module"
    },
    rules:{
        "no-var": 2,
    }
}
// 添加eslint忽略文件.eslintignore,忽略打包后的dist文件夹
dist

Webpack5基础

5.2 babel

作用:将ES6语法转换为向后兼容的JS代码,以便能够运行在当前和旧版本的浏览器中

  1. 配置文件
  • babel.config.*:新建位于根目录的文件,格式为.js或者.json
  • .babelrc.*:新建位于根目录的文件,格式为.js或者.json
  • package.json的babel:直接在package文件中添加配置
  1. 使用
// 下载
npm install -D babel-loader @babel/core @babel/preset-env

//  使用加载器
{
    test: /\.m?js$/,
    exclude: /(node_modules)/, // 忽略node包
    loader: 'babel-loader',
}
// 添加外部的babel.config.js文件,配置预设规则
module.exports = {
    presets: ['@babel/preset-env'] // 智能预设
}

6. 处理html资源

作用:自动生成一个HTML5文件, 在body中使用script标签引入所有webpack生成的bundle

// 下载
npm install --save-dev html-webpack-plugin

// 使用插件
plugins:[
    new ESLintPlugin({
        context: path.resolve(__dirname,"src")
    }),
    new HtmlWebpackPlugin({
        // 配置模版,生成的html文件中会自动保留模板格式
        template: path.resolve(__dirname,"public/index.html")
    })
],

Webpack5基础
Webpack5基础

第三章 搭建开发服务器

1. 自动化打包

作用:自动化编译代码,取消手动输入npx webpack指令操作

// 下载
npm install --save-dev webpack-dev-server

// 添加配置项
module.esports = {
    // 开发服务器配置
    devServer:{
        host: "localhost", // 启动服务器域名
        port: "3000", // 启动服务器端口号
        open: true // 是否自动打开浏览器
    },
}
// 使用
npx webpack server

浏览器自动弹出3000窗口
Webpack5基础

2. 生产模式

生产模式即开发完部署上线,生产模式需要对代码进行优化,让其运行性能更好。优化主要从两个角度出发:

  • 优化代码运行性能
  • 优化代码打包迪度

一般在项目中会拆分生产模式和开发模式的配置文件,并在package.json中通过不同的指令分别启动。

Webpack5基础

3. 生产模式优化配置

3.1 提取输出CSS文件

插件:MiniCssExtractPlugin

作用:将CSS提取到单独的文件中,为每个包含CSS的JS文件创建一个CSS文件,并且支持CSS和SourceMaps的按需加载。

  • MiniCssExtractPlugin.loader:将css文件以link方式引入
  • style-loader:将css样式放到style内联样式中
// 下载
npm install --save-dev mini-css-extract-plugin
// 使用 MiniCssExtractPlugin.loader代替style-loader,并在plugin中引入
module: {
    rules: [
        {
            test: /\.css$/i, // 正则表达式匹配文件
            use: [MiniCssExtractPlugin.loader, "css-loader"], 
        },
    ]
},

plugins:[
    new MiniCssExtractPlugin({
        filename: "css/main.css"
    })
],

MiniCssExtractPlugin-loader
Webpack5基础
style-loader

Webpack5基础

3.2 压缩CSS文件

生产模式默认开启了js和html压缩,针对css,需要使用插件对其进行压缩。

插件:ss-minimizer-webpack-plugin

// 下载
npm install css-minimizer-webpack-plugin --save-dev

// 使用
plugins:[
    new CssMinimizerPlugin()
]

第四章 Webpack优化设置

1. 提升开发体验

1.1 SourceMap

SourceMap是一个源代码映射的系统,可以生成源代码与构建后代码一一映射的文件。SourceMap会生成一个xxx.map文件,里面包含源代码和构建后代码在每一行、每一列的映射关系,当构建后代码出错了,会通过xxx.map文件,从构建后代码出错位置找到映射后源代码出错位置,从而让浏览器提示源代码文件出错位置,帮助我们更快的找到错误根源。

  • 开发模式:cheap-module-source-map

    优点: 打包编译速度快,只包含行映射

    缺点: 没有列映射

mode: "production",
devtool: "cheap-module-source-map"
  • 生产模式:source-map

    优点:既包含行映射,又包含列映射

    缺点:打包编译速度慢

mode: "production",
devtool: "source-map"

1.2 模块热替换

模块热替换(HMR)功能会在应用程序运行过程中替换、添加或删除模块,而无需重新加载整个页面。主要是通过以下几种方式,来显著加快开发速度:

  • 保留在完全重新加载页面期间丢失的应用程序状态
  • 只更新变更内容,从而节省开发时间
  • 在源代码中CSS/JS产生修改时,会立刻在浏览器中进行更新,相当于在浏览器devtools中直接更改样式
devServer:{
    host: "localhost", // 启动服务器域名
    port: "3000", // 启动服务器端口号
    open: true, // 是否自动打开浏览器
    hot: true, // 是否开启HMR热模块替换功能,Webpack5默认开启
},

2. 提升打包构建速度

2.1 OneOf

作用:规定一个文件只能被一个loader处理,提升打包速度

module: {
    rules: [{
        oneOf: [
            {
                test: /\.css$/i,
                use: [MiniCssExtractPlugin.loader, "css-loader"],
            },
            {
                test: /\.less$/i,
                use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'],
            },

        ]
    }]
}

2.2 Include/Exclude

针对JS文件做处理,提升打包编译速度。

  • include:包含,只处理xx文件
  • exclude:排除,除了xxx文件以外其他文件都需要处理
{
    test: /\.m?js$/,
    exclude: /(node_modules)/, // 忽略node包
    loader: 'babel-loader',
}
{
    test: /\.m?js$/,
    include: path.resolve(__dirname,"./src"),
    loader: 'babel-loader',
}
new ESLintPlugin({
    context: path.resolve(__dirname, "src"),
    exclude: "node_modules", // exclude的默认值
}),

3. 减少代码体积

3.1 Cache

每次打包时js文件都要经过Eslint检查和Babel编译,速度比较慢。我们可以缓存之前的Eslint检查和Babel编译的结果,这样第二次打包时速度就会更快了。

// 缓存babel编译
{
    test: /\.m?js$/,
    include: path.resolve(__dirname,"./src"),
    loader: 'babel-loader',
    options:{
        cacheDirectory: true, // 开启babel缓存
        cacheCompression: false // 关闭缓存文件压缩
    }
}

// 缓存eslint检查
plugins: [
    new ESLintPlugin({
        context: path.resolve(__dirname, "src"),
        exclude: "node_modules", // exclude的默认值
        cache: true, // 开启缓存
        cacheLocation: path.resolve(__dirname, "./node_modules/.cache/eslintcache") // 缓存路径
    }),
]

3.2 Thead

当项目越来越庞大时,打包速度就会越来越慢,影响最严重的就是JS的打包速度。而对js文件处理主要就是eslint、babel、Terser三个工具,所以要想提升js文件的运行速度,可以开启多进程同时处理js文件,从而提升打包速度。

多进程打包指的是开启电脑的多个进程同时干一件事,速度更快。

⚠️:请在特别耗时的操作中使用,因为每个进程启动就有大约600ms左右开销。而启动进程的数量不得大于CPU的核数

1)获取CPU核数

const os = require("os");
const threads = os.cpus().length;

2)babel解析:开启多进程,设置进程数量

{
    test: /\.m?js$/,
    include: path.resolve(__dirname, "./src"),
    use: [
        {
            loader: 'thread-loader', // 开启多进程
            options: {
                works: threads // 进程数量
            }
        },
        {
            loader: 'babel-loader',
            options: {
                cacheDirectory: true, // 开启babel缓存
                cacheCompression: false // 关闭缓存文件压缩
            }
        },
    ],
}

3)eslint校验:开启多进程,设置进程数量

optimization: {
    minimizer: [
        new CssMinimizerPlugin(), // 压缩css
        new TerserWebpackPlugin({ // 开启多进程、设置进程数量
            parallel: threads
        })
    ]
}

3.3 Tree Shaking

用于描述移除JavaScript中的没有使用的代码,前提是必须依赖ES Hodule模块化。

Webpack5目前已经内置了Tree Shaking,无需过多的配置。

3.4 Babel文件体积优化

Babel为编译的每个文件都插入了辅助代码,如公共方法的辅助代码_extend。但是有一些辅助代码会被重复添加到每一个需要它的文件中,造成文件体积过大。通过将捕助代码作为一个独立模块,从而避免重复引入问题。

@babel/plugin-transform-runtime:禁用了Babel自动对每个文件的 runtime注入,改为引入@babel/plugin-transform-runtin内的所有辅助代码。通过减少定义和重复引入,从而减少文件体积。

// 下载
npm i @babel/plugin-transform-runtime -D

// 使用
{
    loader: 'babel-loader',
    options: {
        cacheDirectory: true, // 开启babel缓存
        cacheCompression: false, // 关闭缓存文件压缩
        plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
    }
}

3.5 图片压缩

image-minimizer-webpack-plugin插件,可以对本地静态图片进行压缩处理,从而减少代码体积。

压缩图片的模式分为有损压缩和无损压缩两种:

  • 无损压缩:imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo
  • 有损压缩:imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo
// 下载
npm i image-minimizer-webpack-plugin imagemin -D

// 使用-无损压缩
new ImageMinimizerPlugin({
    minimizer: {
        implementation: ImageMinimizerPlugin.imageminGenerate,
        options: {
            plugins: [
                ['gifsicle',{interlaced: true}],
                ['jpegtran',{progressive: true}],
                ['optipng',{optimizationLevel: 5}],
                [                    'svgo',                    {                        plugins: [                            'preset-default',                            'prefixIds',                            {                                name: 'sortAttrs',                                params: {xmlnsOrder: 'alphabetical'}                            }                        ]
                    }
                ]
            ]
        }
    }
})

4. 优化代码运行性能

4.1 Code Split

Code Split,通过将代码分割打包,从而可以按需加载,优化加载速度。

代码分调的作用:

  • 分剩文件:将打包生成的文件进行分割,生成多个js文件
  • 按需加载:需要哪个文件就加载哪个文件

1)多入口打包

entry:{
    app: "./src/app.js",
    main: "./src/main.js"
}

2)多入口提取公共模块

optimization: {
    splitChunks: {
        chunks: "all"
    }
}

3)多入口按需加载

// import()动态加载语法,返回值为promise对象
import(./count.js).then((res)=>{
    ...
}).catch((err)=>{
    ...
})

4)模块命名

// 对某个引入模块命名
import(/* webpackChunkName:"math" */'./math.js')

// 打包输出文件名称使用
module.exorts = {
    output:{
        chunkFilename:"static/js/[name].js"
    }
}

4.2 Preload/Prefetch

1)含义

  • Preload:立即加载资源
  • Prefetch:在浏览器空闲时才开始加载资源

2)共同点

  • 只加载资源,不会执行资源
  • 可以缓存资源

3)区别

  • Preload加载优先级高,Prefetch加载优先级低
  • Preload只能加载当前页面需要使用的资源,Prefetch可以加载当前页面使用到的资源,也可以加载下一个页面使用到的资源

4)适用场景

  • 当前页面优先级高的资源用Preload加载
  • 下一个页面需要使用的资源用Prefetch加载

4.3 Core-js

core-js是用来做ES6以及以上API的polyfill的工具。polyfill翻译过来叫做垫片/补丁,就是用社区上提供的一段代码,让我们在不兼容某些新特性的浏览器上使用该新特性。

core-js一般会和babel一起使用,为ES6及以上语法生成对应的兼容性实现方案,并且会在dist文件夹下面生成一个新的打包文件。

// 下载
npm i core-js

// 配合babel.config.js使用
module.exports = {
    presets: [
        [
            '@babel/preset-env',
            {
                useBuiltIns: "usage", // 按需加载自动引入
                coreJs: 3
            }
        ]
    ]
}

4.4 PWA

浙进式网络应用程序(progressive web application PWA)是一种可以提供类似于native app(原生应用程序)体验的Web App的技术。其中最重要的是在离线(offline) 时,应用程序能够继续运行功能。

其内部是通过Service Workers技术实现的。

// 下载
npm i workbox-webpack-plugin --save-dev

// 添加配置
plugins: [
    new WorkboxPlugin({
        clientsClaim: true,
        skipWaiting: true
    })
]

// 使用-main.js
if("servicelorker" in navigator{
    window.addEventListener("load", ()=>{
        navigator.serviceWorker
            .register("/service-worker.js")
            .then((registration)=>{console.lor("sW registered: ",registration)})
            .catch((registrationError)=>{console.log("sW registration failed: ", reristrationError)})

5. 性能优化概述

1)提升开发体验

  • source Map:开发或上线时代码报错能有更加准确的错误提示

2)提升打包构建速度

  • HMR:开发时只重新编译打包更新变化了的代码,不变的代码使用缓存,从而使更新速度更快
  • oneOf:资源文件一旦被某个loader处理了,就不会继续遍历其他loader,打包速度更快
  • Include/Exclude:排除或只检测某些文件,处理的文件更少,速度更快
  • Cache:对eslint和babel处理的结果进行缓存,让第二次打包速度更快
  • Thead:多进程处理esint和babel任务,速度更快(需要注意的是,进程启动通信都有开销的,要在比较多代码处理时使用才有效)

3)减少代码体积

  • Tree shaking:移除没有使用的多余代码,让代码体积更小
  • babel/plugin-transform-runtime:对babel进行处理,让辅助代码从中引入,而不是每个文件都生成辅助代码,从而体积更小
  • Image hininizer:对项目中图片进行压缩,体积更小,请求速度更快。(本地项目静态图片才需要进行压缩)

4)优化代码运行性能

  • code Split:对代码进行分割成多个js文件,从而使单个文件体积更小,并行加载速度更快
  • import():动态导入语法,按需加载
  • Preload/Prefetch:对代码进行提前加载,提升用户体验
  • Network cache:对输出资源文件进行更好的命名,利于缓存处理,提升用户体验
  • core-js:对js进行兼容性处理,使代码能运行在低版本浏览器中
  • PWA:实现代码离线访问功能,提升用户体验

第五章 webpack核心模块

1. Loader

Loader是帮助webpack将不同类型的文件转换为webpack可识别的模块。

1)Loader优先级分类

  • pre:前置loader
  • normal:普通loader
  • inline:内联loader
  • post:后置loader

2)不同优先级Loader的执行顺序

  • 不同优先级loader:pre > normal >inline > post
  • 相同优先级loader:从右到左,从下到上

3)配置Loader优先级

  • 配置方式:在webpack.config.js文件中指定loader为pre、normal、post loader中一种,不添加任何指定时,默认为normal loader
  • 内联方式:在每个import语句中显式指定loader为inline loader
// pre loader
{
    enforce: "pre"
    test: /\.js$/,
    loader: "loader1"
},
// normal loader
{
    test: /\.js$/,
    loader: "loader2"
}
// post loader
{
    enforce: "post"
    test: /\.js$/,
    loader: "loader3"
}
// inline loader
// 使用style-loader和css-loader处理styles.css文件
import Styles from 'style-loader!css-loader?modules!./styles.css";

// 前面添加一个“!”,跳过normal loader
import Styles from '!style-loader!css-loader?modules!./styles.css";

// 前面添加一个“-!”,跳过 pre、normal loader
import Styles from '-!style-loader!css-loader?modules!./styles.css";

// 前面添加一个“!!”,跳过pre、normal、postloader
import Styles from '!!style-loader!css-loader?modules!./styles.css";

4)Loader底层原理

loader在底层就是一个函数,当webpack解祈资源时,会调用相应的loader方法处理。loader()接收三个参数:

  • content:文件内容
  • map:与SourceMap相关数据
  • meta:其他loader传递的数据
module.exports = function(content, map, meta){
    ... ....
    return content;
}

5)Loader分类

  • 同步loader
// 只有一个loader时
module.exports = function(content, map, meta){
    // 不需要向下传递参数和source-map
    return content;
}

// 有多个loader连用时
module.exports = function(content, map,meta){
    // err:代表是否有错误
    // content:处理后的内容
    // source-map:继续向下传递source-map
    // meta:给下一个loader传递的参数
    this.callback(null,content,map,meta);
}
  • 异步loader
module.exports = function(content , map, meta){
    // 获取异步的回调函数
    const callback = this.async();
    setTimeout(() => {
        // 参数和同步回调函数一致
        callback(null, content, map, meta);
    }, 1000);
}
  • Raw loader
    raw loader表示具有raw属性的loader,属性值为布尔值。可以是同步也可以是异步的loader,区别是其接收到的content是Buffer格式的流数据
function mayLoader(content){
    // Buffer流,一般用于操作图片、图标等资源文件
    return content;
}

mayLoader.raw = true;
module.exports = mayLoader;
  • pitch loader

pitch loader表示具有pitch属性的loader,属性值为函数。可以是同步也可以是异步的loader,特点是执行顺序会早于其他loader。

如连用三个loader处理文件资源时,会从左到右依次执行每个loader的pitch方法,然后在从右到左依次执行每个loader方法。

正常的pitch函数无返回值,如果在执行过程中某个pitch有返回值,则会中断执行顺序,转而执行前一个pitch所在的loader方法。

module.exports = function(content){
    console.log('normal loader');
    return content;
}

module.exports.pitch = function(){
    consel.log("pitch loader");
}

pitch无返回值时的执行顺序
Webpack5基础

pitch2有返回值时的执行顺序
Webpack5基础

6)Loader常用API

方法名 含义 使用
this.async 异步回调loader,返回this.callback const callback = this.async()
this.callback 同步或异步调用的、可以返回多个结果的函数 this.callback(err,content,sourceMap?,meta?)
this.getOptions(schema) 获取loader的options配置,schema为验证规则 this.getOptions(schema)
this.emitFile 生成一个文件 thisemitFile(name,content,sourceMap)
this.utils.contextify 返回一个相对路径 this.utils.contextify(context,request)
this.utils.absolutify 返回一个绝对路径 this.utils.absolutify(context,request)

7)创建Loader

// 清除console.log
module.exports = function (content){
    return content.replace(/consolel.log\(.* );?/g,"");
}
// 创建适配低版本浏览器的babel-loader
const babel = require( @babel/core");
const schema = require("./schema.json");

module.exports = function (content) {
    // 异步loader
    const callback = this.async();
    const options = this.getOptions(schema);
    // 使用babe1对代码进行编译
    babel.transform(content, options, function(err, result){
        if (err){
            callback(err);
        }else{
            callback(null, result.code);
        }
   });
}

2. Plugin

plugin插件可以扩展webpack,加入自定义的构建行为,使webpack可以执行更广泛的任务,拥有更强的构建能力。

webpack就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。这条生产线上的每个处理流程的职责都是单一的,多个流程之间存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。插件就像是一个插入到生产线中的功能,在特定的时间对生产线上的资源做处理,webpack通过Tapable来组织这条复杂的生产线,webpack在运行过程中广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。webpack的事件流机制保证了插件的有序性,使得整个系统扩展性很好————[深入浅出 Webpack]

站在代码逻辑的角度思考plugin的工作原理:webpack在编译的过程中,会触发一系列Tapable钩子事件,插件的作用就是找到对应的钩子,往钩子中挂载任务,即注册事件。当webpack构建的时候,插件注册的事件就会随着钩子的触发而执行。

1)钩子

钩子的本质就是事件,为了方便开发者直接介入和控制编译过程,webpack把编译过程中触发的各类关键事件封装成事件接口暴露了出来,这些接口被称为hooks(钩子)

2)Tapable

Tapable为webpack提供了统一的插件接口(钩子)类型定义,它是webpack的核心功能库。webpack中目前有十种hooks:

Webpack5基础

Tapable提供了三个方法给插件,用于注入不同类型的自定义构建行为:

  • top:可以注册同步钩子和异步钩子
  • topAsync:回调方式注册异步钩子
  • tapPromise:Promise方式注册异步钩子

3)Compiler对象

compiler对象中保存着完整的Webpack环境配置,每次启动webpack构建时,都会创建一个独一无二的compiler,它有以下主要属性:

  • compiler.options:访问本次启动webpack时所有的配置文件,包括但不限于 loaders、entry、output、plugin等等完整配置信息。
  • compiler.inputFileSystem、compiler.outputFileSysten:进行文件操作,相当于Nodejs中fs
  • compiler.hooks:注册tapable的不同种类Hook,从而可以在compiler生命周期中植入不同的逻辑

4)Compilation

compilation对象代表一次资源的构建,compilation实例能够访问所有的模块和它们的依赖。一个compilation对象会对构建依赖图中所有横块进行编译,在编译阶段横块会被加载(load)、封存(seal)、优化(optimize)、分块(chunk)、哈希(hash)和重新创建(restore)。

它有以下主要属性:

  • compilation.modules:访问所有横块,打包的每一个文件都是一个横块
  • compilation.chunks:chunk即是多个modules组成而来的一个代码块,入口文件引入的资源组成一个chunk,通过代码分割的模块又是另外的chunk
  • compilation.assets:访问本次打包生成所有文件的结果
  • compilation.hooks:注册tapable的不同种类Hook,用于在compilation编译模块阶段进行逻辑添加以及修改

5)plugin生命周期

Webpack5基础

6)创建插件

/* 
    1. 加载webpack.config.js中所有配置,调用new TestPlugin(),执行插件的constructor
    2. 创建compiler对象
    3. 遍历所有plugins中的插件,调用插件的apply方法
    4. 执行所有编译流程 (触发hooks事件)
    class TestPlugin{
        constructor(){}
        apply(compiler){
            // 挂载钩子
            compiler.hooks.environmenttap("TestPlugin",()=>{})
            compiler.hooks.emit.tap("TestPlugin",(compilation)=>{}
            compiler.hooks.emit.tapAsync("Testplugin",(compilation, callback) => {
                setTimeout(()=>{
                    callback();
                }, 2000);
            })
        }
    }

    module.exports = TestPlugin;

compiler对象结构
Webpack5基础

compilation对象结构
Webpack5基础

第六章 Webpack原理

1. 核心功能

webpack最核心的功能:内容转化+资源合并

  1. 初始化
  • 初始化参数:参数 = 配置文件、配置对象、Shell 参数 + 默认配置
  • 创建编译器对象:通过参数创建Compiler对象
  • 初始化编译环境:注入内置插件、注册模块工厂、初始化RuleSet集合、加载配置的插件
  • 开始编译:执行compiler对象的run方法
  • 确定入口:根据entry找出所有入口文件
  • 转换入口:调用compilition.addEntry将入口文件转换为dependence对象
  1. 构建
  • 编译模块(make):根据dependence对象创建module对象,调用loader将模块转译为标准JS内容,调用JS解释器将内容转换为AST对象,从中找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
  • 完成模块编译:上一步递归处理所有能触达到的模块后,得到module 集合以及 module之间的依赖关系图
  1. 生成
  • 输出资源(seal):根据入口和模块之间的依赖关系,组装成一个个包含多个模块的Chunk,再把每个Chunk转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会
  • 写入文件系统(emitAssets):在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统

2. 初始化阶段

  • 整合参数:process.args + webpack.config.js
  • 校验参数:validateSchema
  • 合并参数:getNormalizedWebpackOptions + applyWebpackOptionsBaseDefaults
  • 基于参数创建compiler对象:new Compiler
  • 插入外部plugin插件:遍历plugins集合,执行插件的apply方法
  • 加载内置plugin插件:new WebpackOptionsApply().process

Webpack5基础

3. 构建阶段

  • 构建module子类:根据文件类型调用handleModuleCreate 构建 module 子类
  • 转义module内容:调用runLoaders将各类资源转译为 JavaScript 文本
  • 解析JS文本:调用 acorn 将 JS 文本解析为AST
  • 遍历AST,处理依赖

module => AST => dependences => module

🤔:webpack与babel区别?

🙋:相同点:webpack构建阶段会读取源码,解析为AST集合,babel解析阶段会读取源码解析为AST集合。不同点:Webpack只遍历AST集合,babel会对AST做等价转换

🤔:webpack如何识别资源依赖?

🙋:遍历AST集合,通过识别require/import之类的导入语句,确定依赖关系

Webpack5基础

Webpack5基础

Webpack5基础

4. 生成阶段

  • 构建chunkGroup对象
  • 将module分配给chunk:遍历compilation.modules集合,将module按entry/动态引入的规则分配给不同的Chunk对象
  • 记录assets输出规则:遍历module/chunk ,调用compilation.emitAssets方法将assets信息记录到 compilation.assets对象中
  • 将assets写入文件系统
  • 控制流回转到compiler对象

entry及entry触达到的模块,组合成一个chunk;

使用动态引入语句引入的模块,各自组合成一个chunk;

5. 资源形态流转

Webpack5基础

1)compiler.make

  • entry文件以dependence对象形式加入compilation的依赖列表,dependence对象记录entry的类型、路径等信息
  • 根据dependence调用对应的工厂函数创建module对象,之后读入 module 对应的文件内容,调用 loader-runner 对内容做转化,转化结果若有其它依赖则继续读入依赖资源,重复此过程直到所有依赖均被转化为module

2)compilation.seal

  • 遍历module集合,根据entry配置及引入资源的方式,将module分配到不同的chunk
  • 遍历 chunk 集合,调用compilation.emitAsset方法标记chunk的输出规则,即转化为assets集合

3)compiler.emitAssets:将assets写入文件系统

原文链接:https://juejin.cn/post/7320231441705730048 作者:喝咖啡的女孩

(0)
上一篇 2024年1月5日 下午4:16
下一篇 2024年1月5日 下午4:27

相关推荐

发表回复

登录后才能评论