目前最流行的前端打包工具-webpack

简介

webpack 是一种前端资源构建工具,一个静态模块打包器(module bundler)。

核心概念

Entry

入口(Entry)指示 webpack 以哪个文件为入口起点开始打包,分析构建内部依赖图。

Output

输出(Output)指示 webpack 打包后的资源 bundles 输出到哪里去,以及如何命名。

Loader

Loaderwebpack 能 够 去 处 理 那 些 非 JavaScript 文 件 (webpack 自 身 只 理 解 JavaScript)

Plugins

插件(Plugins)可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩, 一直到重新定义环境中的变量等。

Mode

模式(Mode)指示 webpack 使用相应模式的配置。

常见配置

安装

npm init
npm install webpack webpack-cli -g   全局安装webpack
npm install webpack webpack-cli -D

配置

无配置打包

开发环境指令:webpack src/js/index.js -o build/js/built.js --mode=development

功能:webpack 能够编译打包 jsjson 文件,并且能将 es6 的模块化语法转换成 浏览器能识别的语法。

生产环境指令:webpack src/js/index.js -o build/js/built.js --mode=production

配置环境

webpacknode写出来,所以需要使用node的语法

即引入文件的时候使用CommonJS规范的require引入,而不能使用ES6import引入。

loader

loader执行顺序从下到上,从右到左

css

如果要在打包中用到css就需要使用到这个loader

npm i css-loader style-loader -D
npm install --save-dev mini-css-extract-plugin css-loader
test: /\.css$/,
// 使用哪些 loader 进行处理
use: [
    // use 数组中 loader 执行顺序:从右到左,从下到上 依次执行
    // 创建 style 标签,将 js 中的样式资源插入进行,添加到 head 中生效
    'style-loader',
    // 将 css 文件变成 commonjs 模块加载 js 中,里面内容是样式字符串
    'css-loader'
]
module: {
    rules: [
        {
        test: /\.css$/,
            use: [
                // 创建 style 标签,将样式放入
                // 'style-loader',
                // 这个 loader 取代 style-loader。作用:提取 js 中的 css 成单独文件
                MiniCssExtractPlugin.loader,
                // 将 css 文件整合到 js 文件中
                'css-loader'
            ]
        }
    ]
}
plugins: [
    new HtmlWebpackPlugin({
    	template: './src/index.html'
    }),
    new MiniCssExtractPlugin({
    	// 对输出的 css 文件进行重命名
    	filename: 'css/built.css'
    })
],

兼容性处理

npm install --save-dev postcss-loader postcss-preset-env
use: [
    MiniCssExtractPlugin.loader,
    'css-loader',
    {
        loader: 'postcss-loader',
        options: {
            ident: 'postcss',
            plugins: () => [
                // postcss 的插件
                require('postcss-preset-env')()
            ]
        }
    }
]

修改package.json

"browserslist": {
    "development": [
        "last 1 chrome version",
        "last 1 firefox version",
        "last 1 safari version"
    ],
    "production": [
        ">0.2%",
        "not dead",
        "not op_mini all"
    ]
}

提取成单独文件。

npm install --save-dev mini-css-extract-plugin

less

如果想在项目中使用less就需要这个loader

npm i less-loader less -D

图片资源

打包的时候引用到的图片的打包方式。

npm install --save-dev html-loader url-loader file-loader
test: /\.(jpg|png|gif)$/
loader: "url-loader",
options: {
    // 图片大小小于 8kb,就会被 base64 处理
    // 优点: 减少请求数量(减轻服务器压力)
    // 缺点:图片体积会更大(文件请求速度更慢)
    limit: 8 * 1024,
    // 问题:因为 url-loader 默认使用 es6 模块化解析,而 html-loader 引入图片是 commonjs
    // 解析时会出问题:[object Module]
    // 解决:关闭 url-loader 的 es6 模块化,使用 commonjs 解析
    esModules: false,
    // 给图片进行重命名
    // [hash:10]取图片的 hash 的前 10 位
    // [ext]取文件原来扩展名
    name: "[hash:10].[ext]"
}

其他资源

即除了制定的资源以外的资源的打包方式。

{
    // 排除 css/js/html 资源
    exclude: /\.(css|js|html|less)$/,
    loader: 'file-loader',
    options: {
    name: '[hash:10].[ext]'
    }
}

plugins 插件

HTML

npm install --save-dev html-webpack-plugin
// plugins 的配置
// html-webpack-plugin
// 功能:默认会创建一个空的 HTML,自动引入打包输出的所有资源(JS/CSS)
// 需求:需要有结构的 HTML 文件
new HtmlWebpackPlugin({
        // 复制 './src/index.html' 文件,并自动引入打包输出的所有资源(JS/CSS)
        template: './src/index.html'
})

压缩

new HtmlWebpackPlugin({
    template: './src/index.html',
    // 压缩 html 代码
    minify: {
    // 移除空格
    collapseWhitespace: true,
    // 移除注释
    removeComments: true
    }
})

压缩CSS

npm install --save-dev optimize-css-assets-webpack-plugin

使用方法:

直接在插件处添加:

new OptimizeCssAssetsWebpackPlugin()

js

语法检查eslint,如果要多人合作开发项目或者开发一个大型项目,eslint非常有必要,它不仅可以检测出代码中潜在的一些BUG,还可以将代码的风格进行统一。

npm install --save-dev eslint-loader eslint eslint-config-airbnb-base eslint-plugin-import
/*语法检查: eslint-loader eslint
注意:只检查自己写的源代码,第三方的库是不用检查的
设置检查规则:
package.json 中 eslintConfig 中设置~
"eslintConfig": {
"extends": "airbnb-base"
}
airbnb --> eslint-config-airbnb-base eslint-plugin-import eslint
*/
{
    test: /\.js$/,
    exclude: /node_modules/,
    loader: 'eslint-loader',
    options: {
    // 自动修复 eslint 的错误
        fix: true
    }
}

修改package.json

"eslintConfig": {
  "extends": "airbnb-base",
  "env": {
    "browser": true
  }
兼容性处理

在不同的浏览器中,js支持的语法可能也不相同,所以可以指示 babel 做怎么样的兼容性处理。

npm install --save-dev babel-loader @babel/core @babel/polyfill core-js @babel/preset-env
{
    test: /\.js$/,
    exclude: /node_modules/,
    loader: 'babel-loader',
    options: {
        // 预设:指示 babel 做怎么样的兼容性处理
        presets: [
            [
                '@babel/preset-env',
                {
                    // 按需加载
                    useBuiltIns: 'usage',
                    // 指定 core-js 版本
                    corejs: {
                        version: 3
                    },
                    // 指定兼容性做到哪个版本浏览器
                    targets: {
                        chrome: '60',
                        firefox: '60',
                        ie: '9',
                        safari: '10',
                        edge: '17'
                    }
                }
            ]
        ]
    }
}

生产环境会自动压缩js代码

每次打包自动删除

npm install --save-dev clean-webpack-plugin

devserver

如果使用webpack,则每次修改了代码就需要重新进行打包,但是devserver可以实现在你修改代码之后,自动为你打包运行,并且可以模拟出一个服务器。

运行:

npx webpack-dev-server

使用:

devServer: {
    // 项目构建后路径
    contentBase: resolve(__dirname, 'build'),
    // 启动 gzip 压缩
    compress: true,
    // 端口号
    port: 3000,
    // 自动打开浏览器
    open: true
}

环境优化

HMR

  • HMR:hot module replacement 热模块替换/模块热替换作用:一个模块发生变化,只会重新打包这一个模块(而不是打包所有模块)极大提升构建速度。
  • 样式文件:可以使用HMR功能:因为style-loader内部实现了。
  • 。js文件:默认不能使用HMR功能。
  • html文件:默认不能使用HMR功能.同时会导致问题:html文件不能热更新了解决:修改entry入口,将html文件引入。

source-map

devtool: "eval-source-map":一种提供源代码到构建后代码映射技术,即如果代码出现报错,是否会指向源代码中错误的哪一行。

source-map:外部

  • 错误代码准确信息和源代码的错误位置

inline-source-map:内联

  • 只生成一个内联source-map
  • 错误代码准确信息和源代码的错误位置

hidden-source-map:外部

  • 错误代码错误原因,但是没有错误位置
  • 不能追踪源代码错误,只能提示到构建后代码的错误位置

eval-source-map:内联

  • 每一个文件都生成对应的source-map,都在eval错误代码准确信息和源代码的错误位置

nosources-source-map:外部

  • 错误代码准确信息,但是没有任何源代码信息

cheap-source-map:外部

  • 错误代码准确信息和源代码的错误位置只能精确到行

cheap-module-source-map:外部

  • 错误代码准确信息和源代码的错误位置

开发环境

速度快,调试更友好

速度快(eval>inline>cheap>...

  • eval-cheap-souce-map
  • eval-source-map

调试更友好

  • souce-map
  • cheap-module-souce-map
  • cheap-souce-map

推荐选择

  • eval-source-map:调试最友好(React、Vue默认使用)。
  • eval-cheap-module-souce-map:性能更友好。

生产环境

源代码要不要隐藏?调试要不要更友好

内联会让代码体积变大,所以在生产环境不用内联

nosources-source-map全部隐藏

hidden-source-map只隐藏源代码,会提示构建后代码错误信息

推荐选择

  • source-map:调试最友好(推荐生成环境使用)。
  • cheap-module-souce-map:速度快。

缓存

cacheDirectory:true文件资源缓存

hash:每次wepack构建时会生成一个唯一的hash值。

  • 问题:因为js和css同时使用一个hash值。
  • 如果重新打包,会导致所有缓存失效。(可能我却只改动一个文件)

chunkhash:根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样

  • 问题:js和css的hash值还是一样的
  • 因为css是在js中被引入的,所以同属于一个chunk

contenthash:根据文件的内容生成hash值。不同文件hash值一定不一样

tree shaking

去除应用程序中没有使用的代码

  1. 必须使用ES6模块化
  2. 开启production环境

减少代码体积

package.json中配置

"sideEffects":false 所有代码都是没有副作用的代码,都可以进行tree shaking

可能会把css/@babel/polyfill去除

code split

  1. 可以将node_modules中代码单独打包一个chunk最终输出
  2. 自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk
optimization:{
    splitChunks:{
    chunks:'all'
	}
},
    
/*
通过s代码,让某个文件被单独打包成一个chunk
import动态导入语法:能将某个文件单独打包
*/
import(/* webpackChunkName :"test",webpackPrefetch:true */"./test")

webpackPrefetch 预加载,会在使用之前加载

存在兼容性问题 慎用

正常加载可以认为是并行加载 预加载为浏览器空闲时才加载

PWA

渐进式网络开发应用程序(离线访问技术),即没有网络的情况,也可以打开界面。

sw代码必须运行在服务器上

mpn i workbox-webpack-plugin -d
new WorkboxwebpackPlugin.GenerateSw({
    /*
    1.帮助serviceworker快速启动
    2.删除旧的 serviceworker生成一个serviceworker配置文件。
    */
    clientsClaim:true,
    skipWaiting:true
    })

多进程打包

thread-loader开启多进程打包。

进程启动大概为600ms,进程通信也有开销。

只有工作消耗时间比较长,才需要多进程打包,如果本身不大,使用多线程打包反而会更慢。

externals

externals:{
	//拒绝jQuery被打包进来
	jquery:'jQuery'
}

dll

将库打包成不同的文件

配置详解

resolve

resolve:{
    //配置解析模块路径别名 缺点没有提示
    alias:{
   		$css: resolve(__dirname,"src/css")
    },
    //配置省略文件路径的后缀名
    extensions:[".js",".json"],
    //告诉webpack解析模块是去找哪个目录
	modules:[resolve(dirname,'../../node_modules'),'node_modules']
}

optimization

optimization: {
splitChunks: {
    chunks: 'all'
    // 默认值,可以不写~
},
    // 将当前模块的记录其他模块的 hash 单独打包为一个文件 runtime
    // 解决:修改 a 文件导致 b 文件的 contenthash 变化
runtimeChunk: {
	name: entrypoint => `runtime-${entrypoint.name}`
},

webpack5

npm iwebpack@next webpack-cli-D
  • 通过持久缓存提高构建性能.
  • 使用更好的算法和默认值来改善长期缓存。
  • 通过更好的树摇和代码生成来改善捆绑包大小。
  • 清除处于怪异状态的内部结构,同时在v4中实现功能而不引入任何重大更改.
  • 通过引入重大更改来为将来的功能做准备,以使我们能够尽可能长时间地使用v5.

添加了用于长期缓存的新算法。在生产模式下默认情况下启用这些功能。

nodejs提供的模块需要手动添加。

webpack内部有chunk命名规则,不再是以id(0,1,2)命名了。

webpack 现在能够处理对嵌套模块的tree shaking

webpack 4默认只能输出ES5代码

webpack5开始新增一个属性output.ecmaVersion,可以生成ES5ES6/ES2015代码

webpack.config.js

webpack常用的配置放在下面,免得每次使用都需要进行配置:

const { resolve } = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCssAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");

process.env.NODE_ENV = "production";
const commonCssLoader = [
    MiniCssExtractPlugin.loader,
    "css-loader",
    {
        loader: "postcss-loader",
        options: {
            ident: "postcss",
            plugins: () => [require("postcss-preset-env")()]
        }
    }
];
module.exports = {
    entry: ["./src/index.js", "./src/index.html"],
    output: {
        filename: "js/built.[contenthash:10].js",
        path: resolve(__dirname, "build"),
        publicPath: "/",
        chunkFilename: "js/[name].[contenthash:10]_chunk.js"
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                enforce: "pre",
                loader: "eslint-loader",
                options: {
                    fix: true
                }
            },
            {
                oneOf: [
                    {
                        test: /\.css$/,
                        use: [...commonCssLoader]
                    },

                    {
                        test: /\.js$/,
                        exclude: /node_modules/,
                        loader: "babel-loader",
                        options: {
                            presets: [
                                [
                                    "@babel/preset-env",
                                    {
                                        useBuiltIns: "usage",
                                        corejs: { version: 3 },
                                        targets: {
                                            chrome: "60",
                                            firefox: "50",
                                            ie: "9",
                                            safari: "10",
                                            edge: "17"
                                        }
                                    }
                                ]
                            ],
                            cacheDirectory: true
                        }
                    },
                    {
                        test: /\.(jpg|png|gif)/,
                        loader: "url-loader",
                        options: {
                            limit: 8 * 1024,
                            name: "[hash:10].[ext]",
                            outputPath: "imgs",
                            esModule: false
                        }
                    },
                    {
                        test: /\.html$/,
                        loader: "html-loader"
                    },
                    {
                        exclude: /\.(js|css|less|html|jpg|png|gif)/,
                        loader: "file-loader",
                        options: {
                            outputPath: "media"
                        }
                    }
                ]
            }]
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: "css/built.[contenthash:10].css"
        }),
        new OptimizeCssAssetsWebpackPlugin(),
        new HtmlWebpackPlugin({
            template: "./src/index.html",
            minify: {
                collapseWhitespace: true,
                removeComments: true
            }
        }),
        new CleanWebpackPlugin()
    ],
    mode: "development",
    devServer: {
        contentBase: resolve(__dirname, "build"),
        compress: true,
        port: 3000,
        open: true,
        clientLogLevel: "none",
        hot: true,
        watchContentBase: true,
        watchOptions: {
            ignored: /node_modules/
        },
        quiet: true
    },
    optimization: {
        splitChunks: {
            chunks: "all"
        },
        runtimeChunk: {
            name:entrypoint =>`runtime-${entrypoint.name}`
        }
    },
    devtool: "source-map"
};

package.json添加

"browserslist": {
  "development": [
    "last 1 chrome version",
    "last 1 firefox version",
    "last 1 safari version"
  ],
  "production": [
    ">0.2%",
    "not dead",
    "not op_mini all"
  ]
},
"eslintConfig": {
  "extends": "airbnb-base",
  "env": {
    "browser": true
  }
}

运行:

npx webpack-dev-server

参考资料

尚硅谷2020最新版Webpack5实战教程(从入门到精通)