从基础到实战 手把手带你掌握新版Webpack4(三)

18.库文件的打包

webpack.config.js

具体代码

const path = require('path');

module.exports = {
    mode: 'production',
    entry: './src/index.js',
    // 忽略 lodash ,不打包
    // externals: ['lodash'],
    externals: {
        // 当用户引用 lodash 这个库文件的时候,必须使用 lodash 这个名字
        lodash: {
            commonjs: 'lodash'
        }
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'library.js',
        library: 'library',
        // 支持 script 标签引入
        libraryTarget: 'umd'
        // import const require 无论什么方法引入,都支持
        // libraryTarget: 'this'
        // library 挂载到 this

    }
}

package.json

"main": "./dist/library.js",
  "scripts": {
    "build": "webpack"
  },

19.PWA的打包(Progressive Web Application)

在离线(offline)时应用程序能够继续运行功能。这是通过使用名为 Service Workers 的 web 技术来实现的。线上环境时才用到pwa,开发时不需要

为了模拟环境,安装npm i http-server -D npm install workbox-webpack-plugin --save-dev
具体代码

package.json

"scripts": {
    "start": "http-server dist",
    "dev": "webpack-dev-server --config ./build/webpack.common.js",
    "build": "webpack --env.production --config ./build/webpack.common.js"
  },

webpack.prod.js

const WorkboxPlugin = require('workbox-webpack-plugin');

// ....

plugins: [
	new MiniCssExtractPlugin({
        filename: '[name].css',
        chunkFilename: '[name].chunk.css'
    }),
    new WorkboxPlugin.GenerateSW({
        clientsClaim: true,
        skipWaiting: true
    })
],

20.TypeScript 的打包配置npm install --save-dev typescript ts-loader npm install --save-dev @types/lodash

webpack.config.js

const path = require('path')

module.exports = {
    mode: 'production',
    entry: './src/index.tsx',
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: 'ts-loader',
                exclude: /node_modules/
            }
        ]
    },
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist')
    }
}

tsconfig.json

{
    "compilerOptions": {
        "outDir": "./dist",
        "module": "es6",
        "target": "es5",
        "allowJs": true
    }
}

typescript

21.WebpackDevServer 实现请求转发npm i axios -D

以 react 为例

index.js
class App extends Component {

    componentDidMount() {
        axios.get('/react/api/header.json').then ((res) => {
            console.log(res)
        })
    }

    render () {
        return <div>hello react</div>
    }
}
webpack.config.js
devServer: {
    contentBase: './dist',
    open: true,
    hot: true,
    hotOnly: true,
    proxy: {
        '/react/api': {
            target: 'http://www.dell-lee.com',
            //对https协议的网址的请求的转发
            secure: false,
            // bypass: function (req, res, proxyOptions) {
            //     // 请求是 html 的情况,直接返回这个 html ,不会走代理
            //     if (req.headers.accept.indexOf('html') !== -1) {
            //         console.log('Skipping proxy for browser request.');
            //         return '/index.html';
            //     }
            // },
            pathRewrite: {
                'header.json': 'demo.json'
            },
            //解决网站对接口的限制
            changeOrigin: true,
            //变更请求头
            headers: {
                host: 'www.dell-lee.com',
                cookie: 'asvafhgs'
            }
        }
    }
},

22.WebpackDevServer 解决单页面应用路由问题npm i react-router-dom --save

proxy: {
    '/react/api': {
        target: 'http://www.dell-lee.com',
        secure: false,
        // historyApiFallback: {
        //     rewrites: [//访问任何路径都展示index.html页面
        //         { from: /\.*/, to: '/index.html' },
        //     ]
        // },
        // 一般配置这个就可以了
        historyApiFallback: true,
        // bypass: function (req, res, proxyOptions) {
        //     // 请求是 html 的情况,直接返回这个 html ,不会走代理
        //     if (req.headers.accept.indexOf('html') !== -1) {
        //         console.log('Skipping proxy for browser request.');
        //         return '/index.html';
        //     }
        // },
        pathRewrite: {
            'header.json': 'demo.json'
        },
        changeOrigin: true,
        headers: {
            host: 'www.dell-lee.com',
            cookie: 'asvafhgs'
        }
    }
}

23.EsLint 在 Webpack 中的配置npm install eslint -D npm install babel-eslint -D npm install eslint-loader -D

//快速生成eslint配置
npx eslint --init

.eslintrc.js

module.exports = {
    "env": {
        "browser": true,
        "es6": true
    },
    "extends": [
        "plugin:react/recommended",
        "airbnb"
    ],
    "globals": {
        "Atomics": "readonly",
        "SharedArrayBuffer": "readonly"
    },
    "parser": "babel-eslint",
    "parserOptions": {
        "ecmaFeatures": {
            "jsx": true
        },
        "ecmaVersion": 2018,
        "sourceType": "module"
    },
    "plugins": [
        "react"
    ],
    "rules": {
    }
};

webpack.config.js

devServer: {
    // eslint 报错显示在浏览器
    overlay: true,
    contentBase: './dist',
},

{
    test: /\.js$/,
    exclude: /node_modules/,
    // use: [
    //     'babel-loader',
    //     {
    //         loader: 'eslint-loader',
    //         options: {
    //             fix: true
    //         }
    //     }
    // ]
    // use: [
    //     {
    //         loader: 'eslint-loader',
    //         options: {
    //             fix: true
    //         },
    //         // 强制执行
    //         force: 'pre'
    //     },
    //     'babel-loader'
    // ]
    loader: 'babel-loader'
},

24. webpack 性能优化

  • 1.使用高版本的 Webpack 和 Node.js

  • 2.多进程/多实例构建:thread-loader, parallel-webpack,happypack

  • 3.压缩代码

    • webpack-paralle-uglify-plugin
    • uglifyjs-webpack-plugin 开启 parallel 参数 (不支持ES6)
    • terser-webpack-plugin 开启 parallel 参数
    • 多进程并行压缩
    • 通过 mini-css-extract-plugin 提取 Chunk 中的 CSS 代码到单独文件,通过 css-loader 的 minimize 选项开启 cssnano 压缩 CSS。
  • 4.图片压缩

    • 使用基于 Node 库的 imagemin (很多定制选项、可以处理多种图片格式)
    • 配置 image-webpack-loader
  • 5.缩小打包作用域

    • exclude/include (确定 loader 规则范围)
    • resolve.modules 指明第三方模块的绝对路径 (减少不必要的查找)
    • resolve.mainFields 只采用 main 字段作为入口文件描述字段 (减少搜索步骤,需要考虑到所有运行时依赖的第三方模块的入口文件描述字段)
    • resolve.extensions 尽可能减少后缀尝试的可能性
    • noParse 对完全不需要解析的库进行忽略 (不去解析但仍会打包到 bundle 中,注意被忽略掉的文件里不应该包含 import、require、define 等模块化语句)
    • IgnorePlugin (完全排除模块)
    • 合理使用alias
  • 6.提取页面公共资源

    • 使用 html-webpack-externals-plugin,将基础包通过 CDN 引入,不打入 bundle 中
    • 使用 SplitChunksPlugin 进行(公共脚本、基础包、页面公共文件)分离(Webpack4内置) ,替代了 CommonsChunkPlugin 插件
    • 基础包分离
  • DLL:

    • 使用 DllPlugin 进行分包,使用 DllReferencePlugin(索引链接) 对 manifest.json 引用,让一些基本不会改动的代码先打包成静态资源,避免反复编译浪费时间。实现第三方模块只打包一次
    • HashedModuleIdsPlugin 可以解决模块数字id问题
  • 充分利用缓存提升二次构建速度:

    • babel-loader 开启缓存
    • terser-webpack-plugin 开启缓存
    • 使用 cache-loader 或者 hard-source-webpack-plugin
  • Tree shaking

    • purgecss-webpack-plugin 和 mini-css-extract-plugin配合使用(建议)
    • 打包过程中检测工程中没有引用过的模块并进行标记,在资源压缩时将它们从最终的bundle中去掉(只能对ES6 Modlue生效) 开发中尽可能使用ES6 Module的模块,提高tree shaking效率
    • 禁用 babel-loader 的模块依赖解析,否则 Webpack 接收到的就都是转换过的 CommonJS 形式的模块,无法进行 tree-shaking
    • 使用 PurifyCSS(不在维护) 或者 uncss 去除无用 CSS 代码
  • Scope hoisting

    • 构建后的代码会存在大量闭包,造成体积增大,运行代码时创建的函数作用域变多,内存开销变大。Scope hoisting 将所有模块的代码按照引用顺序放在一个函数作用域里,然后适当的重命名一些变量以防止变量名冲突
    • 必须是ES6的语法,因为有很多第三方库仍采用 CommonJS 语法,为了充分发挥 Scope hoisting 的作用,需要配置 mainFields 对第三方模块优先采用 jsnext:main 中指向的ES6模块化语法
  • 动态Polyfill

    • 建议采用 polyfill-service 只给用户返回需要的polyfill,社区维护。(部分国内奇葩浏览器UA可能无法识别,但可以降级返回所需全部polyfill)

25.编写一个 Loader

Loader 支持链式调用,所以开发上需要严格遵循“单一职责”,每个 Loader 只负责自己需要负责的事情。
  • Loader 运行在 Node.js 中,我们可以调用任意 Node.js 自带的 API 或者安装第三方模块进行调用
  • Webpack 传给 Loader 的原内容都是 UTF-8 格式编码的字符串,当某些场景下 Loader 处理二进制文件时,需要通过 exports.raw = true 告诉 Webpack 该 Loader 是否需要二进制数据
  • 尽可能的异步化 Loader,如果计算量很小,同步也可以
  • Loader 是无状态的,不应该在 Loader 中保留状态
  • 使用 loader-utils 和 schema-utils 提供的实用工具
mkdir make-loader
cd make-loader
npm init -y
npm i webpack webpack-cli -D
npm i loader-utils -D

webpack.config.js

const path = require('path')

module.exports = {
    mode: 'development',
    entry: {
        main: './src/index.js'
    },
    resolveLoader: {
        modules: ['node_modules', './loader']
    },
    module: {
        rules: [
            {
                test: /\.js/,
                use: [
                    // path.resolve(__dirname, './loader/replaceLoader.js')
                    // {
                    //     loader: path.resolve(__dirname, './loader/replaceLoader.js'),
                    //     options: {
                    //         name: 'webpack'
                    //     }
                    // }
                    path.resolve(__dirname, './loader/replaceLoader.js'),
                    {
                        loader: path.resolve(__dirname, './loader/replaceLoaderAsync.js'),
                        options: {
                            name: 'webpack'
                        }
                    }
                ]
            }
        ]
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    }
}

26.如何编写一个 Plugin

webpack在运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在特定的阶段钩入想要添加的自定义功能。Webpack 的 Tapable 事件流机制保证了插件的有序性,使得整个系统扩展性良好。
  • compiler 暴露了和 Webpack 整个生命周期相关的钩子
  • compilation 暴露了与模块和依赖有关的粒度更小的事件钩子
  • 插件需要在其原型上绑定apply方法,才能访问 compiler 实例
  • 传给每个插件的 compiler 和 compilation对象都是同一个引用,若在一个插件中修改了它们身上的属性,会影响后面的插件
  • 找出合适的事件点去完成想要的功能
    • emit 事件发生时,可以读取到最终输出的资源、代码块、模块及其依赖,并进行修改(emit 事件是修改 Webpack 输出资源的最后时机)
    • watch-run 当依赖的文件发生变化时会触发
  • 异步的事件需要在插件处理完任务时调用回调函数通知 Webpack 进入下一个流程,不然会卡住
const path = require('path')
const CopyrightWebpackPlugin = require('./plugins/copyright-webpack-plugin')

module.exports = {
    mode: 'development',
    entry: {
        main: './src/index.js'
    },
    plugins:[
        new CopyrightWebpackPlugin()
    ],
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    }
}

你可能感兴趣的:(webpack)