1 简介

1.1 webpack 五个核心概念

1.1.1 Entry

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

1.1.2 Output

输出,指示webpack打包后的资源bundles输出到哪里去,以及怎么命名

1.1.3 Loader

加载器,可以让webpack能够去处理非js文件(webpack自身之理解javascript)

1.1.4 Plugins

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

1.1.5 Mode

模式,指示webpack使用相应模式的配置

  • development:能让代码本地调试运行的环境
  • production:能让代码优化上线的运行环境

2 webpack 初体验

2.1 创建一个webpack项目

  1. 创建一个目录,初始化包管理 npm init
  2. 运行命令npm install webpack webpack-cli -D
  3. 新建src文件夹用于存放代码,新建build文件夹用于存放打包好的文件
  4. 编辑代码
  5. 运行命令(开发环境) webpack ./src/index.js -o ./build/build.js --mode=development

默认只能处理js/json文件

2.2 打包css样式资源

  1. 新建css文件style.css

  2. 在index.js中引入 import 'style.css'

  3. 创建webpack.config.js文件来配置webpack(使用commentJS)

    const {
        resolve
    } = require('path');
    
    module.exports = {
        mode: 'development', // 模式,不能同时存在
        // mode: 'production'
        entry: './src/index.js', // 入口文件
        output: {
            filename: 'built.js', // 输出文件名
            path: resolve(__dirname, 'build') // 输出路径
        },
        module: { // loader配置
            rules: [{
                test: /\.css$/,// css结尾的文件
                use: [// 执行顺序:从右到左,从下到上,依次执行
                    'style-loader',// 创建爱你style标签,将js中的样式资源插入,添加到head
                    'css-loader',// 将css文件变成commentjs模块加载js中,内容是字符串
                ]
            }]
        },
        plugins: [ // 插件配置
    
        ],
    }
    
  4. 安装所需要的loader

  5. 运行命令 webpack

使用less、scss等预编译语法需要下载对应的loader,配置方式同上,再添加一个rule对象

2.3 打包HTML文件

  1. 安装打包html的插件 npm install html-webpack-plugin-D

  2. 在webpack.config.js中引入并添加配置

    const HtmlWebpackPlugin = require('html-webpack-plugin');
    ···
    plugins: [ // 插件配置
        // html-webpack-plugin
        // 功能: 创建一个空的html文件引入打包之后的所有资源
        new HtmlWebpackPlugin({
            // 赋值目标文件并引入打包的资源
            template: './src/index.html'
        })
    ],
    

在创建html模板的时候不要引入资源

2.4 打包图片文件

图片添加略过,只给出配置方式

module: { // loader配置
    rules: [{
        test: /\.(jpg|png|gif|jpeg)$/,
        // 只有一个loader的时候可以直接loader+loader名
        loader: 'url-loader',// 下载时需要下载url-loader和file-loader
        options: {
            /**
             * 限制大小,小于限制就会被base64处理
             * 优点减少请求数量,减轻服务器压力
             * 缺点图片会更大,文件请求速度会变慢
             */
            limit: 200 * 1024
        }
    }]
},

如果html模板中添加了图片,添加一个额外的loader来解析

{
    test: /\.html$/,
    loader: 'html-withimg-loader' // 处理html中的图片
}

但是,因为url-loader默认使用es6模块化解析,而html-loader引入图片是commentjs

解析时会出现[object Module]

解决办法:关闭ur-loader的ES6解析,在url-loader的options中添加 esModule: false

2.5 打包其他资源

{
    exclude: /\.(css|html|jpg|png|gif|jpeg)$/,
    loader: 'file-loader'
}

除了这匹配到的后缀文件之外,其他的文件用file-loader打包

2.6 使用devServer

开发服务器:用来自动编译,热更新等

在内存中编译打包,不会有任何输出

运行:webpack-dev-server

  1. 运行npm install webpack-dev-server -D安装

  2. webpack.config.js中添加配置

    devServer: {
        contentBase: resolve(__dirname, 'build'),// 项目构建后的路径
        compress: true,// 启动gzip压缩
        port: 3000,// 端口号
        open: true,// 打开默认浏览器
    }
    
  3. 运行webpack-dev-server

3 开发环境配置

const {
    resolve
} = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    mode: 'development',
    // mode: 'production'
    entry: './src/index.js',
    output: {
        filename: 'built.js',
        path: resolve(__dirname, 'build')
    },
    module: {
        rules: [{
            test: /\.css$/,
            use: [
                'style-loader',
                'css-loader',
            ]
        },{
            test: /\.(jpg|png|gif|jpeg)$/,
            loader: 'url-loader',
            options: {
                limit: 200 * 1024,
                esModule: false
            }
        },{
            test: /\.html$/,
            loader: 'html-loader'
        }]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html'
        })
    ],
    devServer: {
        contentBase: resolve(__dirname, 'build'),
        compress: true,
        port: 3000,
        open: true,
    }
}

4 生产环境搭建

4.1 提取css成单独文件

  1. 安装插件 npm install mini-css-extract-plugin -D

  2. 修改webpack.config.js配置文件

    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    ···
        module: {
            rules: [{
                test: /\.css$/,
                use: [
                    // 'style-loader',
                    MiniCssExtractPlugin.loader,// 替换style-loader,提取css成单独文件
                    'css-loader', 
                ]
            },
            ···]
        },
        plugins: [ 
            ···
            new MiniCssExtractPlugin({
                filename: 'css/style.css'
            })
        ],
    
  3. 运行打包命令

4.2 css兼容性处理

使用到postcss

  1. 运行 npm install postcss-loader postcss-preset-env -D

  2. 修改webpack.config.js的css相关部分配置

    {
        test: /\.css$/, 
        use: [
            MiniCssExtractPlugin.loader,
            'css-loader',
            {
                loader: 'postcss-loader',
                options: {
                    ident: 'postcss',
                    plugins: () => [
                        require('postcss-preset-env')()
                    ]
                }
            }
        ]
    }
    
  3. 在package.json中添加浏览器版本控制

    "browerslist": {
        "development": [
              "last 1 chrome version",
              "last 1 firefox version",
              "last 1 safair version"
        ],
        "production": [
              ">0.2%",
             "not dead",
              "not op_mini all"
        ]
    }
    
  4. 运行打包

4.3 压缩css

使用插件 optimize-css-assets-webpack-plugin

  1. 运行npm install optimize-css-assets-webpack-plugin -D

  2. webpack.config.js中引入

    const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
    ···
    plugins: [
        ···
        new OptimizeCssAssetsWebpackPlugin()
    ]
    

4.4 js语法检查

使用eslint,使用airbnb规则检查js语法

  1. 运行 npm install eslint-loader eslint eslint-config-airbnb-base eslint-plugin-import -D 安装所需的依赖和插件

  2. 在package.json中设置eslintConfig,继承airbnb

    "eslintConfig": {
      "extends": "airbnb-base"
    }
    
  3. webpack.config.js中添加rules

    {
        test: /\.js$/,
        exclude: /node_modules/,// 只检查自己的代码,不检查第三方库中的代码
        loader: 'eslint-loader',
        options: {
            fix: true// 自动修复eslint错误
        }
    }
    
  4. 使用 // eslint-disable-next-line控制下一行不进行检查

4.5 js兼容性处理

使用babel-loader进行兼容性处理

  1. 运行 npm install babel-loader @babel/core @babel/preset-env @babel/polyfill -D

  2. 添加webpack.config.js的rules

    {
        test: /\.js$/,
         exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
            presets: ['@babel/preset-env']
        }
    },
    

@babel/polyfill属于高级语法支持,在需要兼容的js文件中引入即可

import ‘@babel/polyfill’

@babel/polyfill属于暴力支持,回事打包文件变的非常大,所以一般采用按需加载的方式:core.js

  1. 运行 npm install corejs -D

  2. 修改之前的兼容性配置规则

    {
        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'
                        }
                    }
                ]
            ]
        }
    },
    

4.6 HTML和js压缩

4.6.1 js压缩

将模式调整为production即可实现js代码压缩

4.6.2 HTML压缩

在html的插件中添加设置

new HtmlWebpackPlugin({
    // 复制目标文件并引入打包的资源
    template: './src/index.html',
    minify: {
        // 移除空格
        collapseWhitespace: true,
        // 移除注释
        removeComments: true
    }
}),

5 生产环境配置

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

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'js/built.js',
        path: resolve(__dirname, 'build')
    },
    module: { // loader配置
        rules: [{
                test: /\.css$/, 
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    {
                        loader: 'postcss-loader',
                        options: {
                            ident: 'postcss',
                            plugins: () => [
                                require('postcss-preset-env')()
                            ]
                        }
                    }
                ]
            }, {
                test: /\.(jpg|png|gif|jpeg)$/,
                loader: 'url-loader',
                options: {
                    limit: 200 * 1024,
                    esModule: false
                }
            }, {
                test: /\.html$/,
                loader: 'html-loader'
            },
            {
                test: /\.js$/,
                exclude: /node_modules/,
                enforce: 'pre',// 优先执行
                loader: 'eslint-loader',
                options: {
                    fix: true
                }
            }, {
                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'
                                }
                            }
                        ]
                    ]
                }
            },
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html',
            minify: {
                collapseWhitespace: true,
                removeComments: true
            }
        }),
        new MiniCssExtractPlugin({
            filename: 'css/style.css'
        }),
        new OptimizeCssAssetsWebpackPlugin()
    ],
    devServer: {
        contentBase: resolve(__dirname, 'build'),
        compress: true,
        port: 3000,
        open: true,
    },
    // mode: 'development',
    mode: 'production',
}

babel和eslint可能会产生冲突使用enforce: ‘pre’让eslint先执行

6 性能优化篇

6.1 开发环境性能优化

6.1.1 优化打包构建速度

6.1.1.1HMR——hot module replacement(模块热替换)

只会重新打包这一个模块,不会重新打包

配置devServer

devServer: {
    contentBase: resolve(__dirname, 'build'),
    compress: true, // 启动gzip压缩
    port: 3000, // 端口号
    open: true, // 打开默认浏览器
    hot: true,// 开启热更新
},

样式文件热更新基于’style-loader’;

js文件默认不能使用HMR功能;

HTML文件默认不能使用HMR功能,同时会导致问题,HTML文件不能热更新,解决办法:修改entry入口,将html文件引入(不需要做HMR)

6.1.1.2 oneOf减少loader检索

每个文件在检索的的时候会每个rules都验证一遍,将所有的rules都放到oneOf中就可以验证到所在的规则不再继续验证

这样出现了问题,js可能会有多个loader,解决办法:oneOf中保留一条rule,其他的取出来与oneOf平级

6.1.2 优化代码调试

source-map:提供语言代码构建后代码映射技术(构建代码出错,追踪源代码错误)

添加devtool

devtool: 'source-map',

[inline- |hidden- | eval-][nosources-][cheap-[module-]]source-map分别对应:

  • inline-内联:(汇集在一起)错误代码准确信息和源代码错误位置

  • hidden-外部:代码错误原因,没有错误位置,不能追踪代码错误

  • eval-内联:(分别跟每个文件对应)错误代码准确信息和源代码错误位置(位置信息是哈希值)

  • nosources-外部:错误代码准确信息,没有源代码错误信息

  • cheap-外部:错误代码准确信息和源代码错误位置(只精确到行,提示整行错误)

  • cheap-module-外部:错误代码准确信息和源代码错误位置

  • source-map`外部:错误代码准确信息和源代码错误位置:

模式使用

  • 开发环境:速度快,开发更友好

    • 速度快eval>inline>cheap>···
      • eval-cheap-source-map
      • eval-source-map
    • 调试更友好
      • source-map
      • cheap-module-source-map
      • cheap-source-map

    –>eval-source-map / eval-cheap-module-source-map

  • 生产环境:源代码要不要隐藏

    • 内联会让代码体积变大,使用外部方式
      • source-map
      • ···
    • 隐藏代码
      • nosource-source-map// 全部隐藏
      • hidden-source-map// 隐藏源代码

    –>source-map / cheap-module-source-map

6.2生产环境性能优化

缓存机制

babel缓存

修改js打包配置,开启缓存

{
    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'
                    }
                }
            ]
        ],
        cacheDirectory: true// 开启缓存
    }
},

强缓存模式,所有的数据都要缓存,从新打包之后不会试用新的文件

文件资源缓存
hash

这里修改输出文件名,每次到打包之后的文件都不一样,这就不加绝了强制缓存带来的不刷新了(取10位hash值)

output: {
    filename: 'js/built[hash:10].js', // 输出文件名
    path: resolve(__dirname, 'build') // 输出路径
},

所有的js和css文件共用一个哈希值,重新打包会导致所有的缓存失效

chunkhash

根据chunk生成的hash值,如果打包源来自同一个chunk,那么hash值相同

此时,js和css的hash值还是相同,因为css是在js中引入的,同属于一个chunk

contenthash

根据文件内容生成hash,不同文件hash值一定不一样

filename: 'js/built[contenthash:10].js'

如果单独提取了css,css命名修改为

new MiniCssExtractPlugin({
    filename: 'css/style-[contenthash:10].css'
})

tree shaking

去除无用代码

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

打包是会自动摇去没有用的叶子

在package.json中配置"sideEffects": false所有代码都没有副作用

​ 问题:可能会把css文件干掉,解决"sideEffects": ["*.css"]

code split

文件分割

  • 多入口文件分割

  • 添加webpack.config.js配置项,将node中的代码单独打包一个chunks,自动分析多入口文件中有没有公共文件,如果有会单独打包成一个chunk

optimization: {
    splitChunks: {
        chunks: 'all'
    }
},
  • 通过js代码,让某个文件单独打包成一个chunk

懒加载、预加载

懒加载
document.getElementById('btn').onclick = function() {
    import(/* webpackChunkName: 'test'*/'./js/test.js')
        .then(({mul}) => {
            console.log(mul(4, 5));
        })
}

将引入放在事件的回调函数中,事件触发时才加载js文件

配合eslint语法检查是有问题的,在.eslintrc文件汇总设置 "allowImportExportEverywher": true,允许在任何地方import

预加载

将引入改为

import(/* webpackChunkName: 'test', webpackPrefetch: true */'./js/test.js')

预加载会提前将js文件加载

区别
  • 懒加载:文件需要才加载
  • 预加载:使用之前加载,浏览器空闲再加载(兼容性较差)
  • 正常加载:并行加载

PWA

渐进式网络开发应用程序(离线可访问)

​ workbox–>workbox-webpack-plugin

  1. 运行 npm install workbox-webpack-plugin -D

  2. 修改webpack.config.js配置

    const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
    ···
    plugin: [
        ···
        new WorkboxWebpackPlugin.GenerateSW({
            clientsClaim: true,
            skipWaiting: true
        })
    ]
    

    帮助serviceworker快速启动;删除旧的serviceworker

  3. 在入口文件中注册serviceworker,处理兼容

    if('serviceWorker' in navigator) {
        window.addEventListener('load', () => {
            navigator.serviceWorker.register('./service-worker.js')
                .then(() => {
                console.log('sw注册成功')
            }).catch(() => {
                console.log('sw注册失败')
            })
        })
    }
    
  4. eslint不识别window,navigator等全局变量,修改package.json配置

    "eslintConfig": {
        "extends": "airbnb-base",
        "env": {
            "browser": true// 支持浏览器全局变量
        }
    }
    
  5. 构建代码

多进程打包

消耗时间长的才需要多进程打包,多进程开始需要时间,进程通信也需要时间

  1. 运行npm install thread-loader -D

  2. 修改webpack.config.js中babel所在的rule

    {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
            {
                loader: 'thread-loader',// 多进程打包
                options: {
                    workers: 2,// 使用两个进程
                }
            },
            {
                loader: 'babel-loader',
                ···
            }
        ]
    },
    

externals

忽略某些包,比如CDN引入的

在webpack.config.js第一级目录下添加

externals: {
    // 包名: npm库名
    jquery: 'jQuery'
}

dll

  1. 新建一个配置文件,例如 webpack.dll.js

    const {resolve} = require('path');
    const webpack = require('webpack');
    
    module.exports = {
        entry: {
            // [name]: 要打包的库名
            jquery: ['jquery']
        },
        output: {
            filename: '[name].js',
            path: resolve(__dirname, 'dll'),
            library: '[name]_[hash]'// 打包库里暴露的内容叫什么名
        },
        plugins: [
            // 打包生成一个映射关系
            new webpack.DllPlugin({
                name: '[name]_[hash]',// 映射库暴露的内容名称
                path: resolve(__dirname, 'dll/manifast.json')// 输出文件路径
            })
        ],
        mode: 'production'
    }
    
  2. 运行命令 webpack --config webpack.dll.js

  3. 运行 npm install add-asset-html-webpack-plugin -D

  4. 修改webpack.config.js配置

    const webpack = require('webpack');
    const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
    ···
    plugins: [
        ···
        // 告诉webpack那些文件不需要打包,以及映射关系
        new webpack.DllReferencePlugin({
            manifest: resolve(__dirname, 'dll/manifest')
        }),
        // 将文件打包输出,并在html自动引入该文件
        new AddAssetHtmlWebpackPlugin({
            filepath: resolve(__dirname, 'dll/')
        })
    ]
    

前端小白