学习笔记:webpack实践

186 阅读6分钟

之前做的两个项目都是用create-react-app脚手架生成,无需关注配置,直接写业务就ok,现在有新项目了,要自己配置,那就只能看看官网,再看看解析教程之类的,先简单了解一下webpack的基础。

1.是什么

webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部构建一个 依赖图(dependency graph),此依赖图对应映射到项目所需的每个模块,并生成一个或多个 bundle。

从官方介绍可知,webpack就是一个静态模块的打包工具,就是图片,css,js等生成 JS 格式的 bundler 文件。这是其基础的核心作用。

构建的核心流程

初始化阶段:

  1. 初始化参数:从配置文件、 配置对象、Shell 参数中读取,与默认配置结合得出最终的参数
  2. 创建编译器对象:用上一步得到的参数创建 Compiler 对象
  3. 初始化编译环境:包括注入内置插件、注册各种模块工厂、初始化 RuleSet 集合、加载配置的插件等
  4. 开始编译:执行 compiler 对象的 run 方法
  5. 确定入口:根据配置中的 entry 找出所有的入口文件,调用 compilition.addEntry 将入口文件转换为 dependence 对象

构建阶段:

  1. 编译模块(make):根据 entry 对应的 dependence 创建 module 对象,调用 loader 将模块转译为标准 JS 内容,调用 JS 解释器将内容转换为 AST 对象,从中找出该模块依赖的模块,再 递归 本步骤直到所有入口依赖的文件都经过了本步骤的处理
  2. 完成模块编译:上一步递归处理所有能触达到的模块后,得到了每个模块被翻译后的内容以及它们之间的 依赖关系图

生成阶段:

  1. 输出资源(seal):根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会
  2. 写入文件系统(emitAssets):在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统

2.核心概念

1. entry (入口)

webpack根据此配置,找到构建其内部依赖图的开始,默认值是./src/index.js,可以配置一个或多个不同的入口起点。

单个入口的写法

module.exports = {
  entry: './path/to/my/entry/file.js',
};

module.exports = {
  entry: {
    main: './path/to/my/entry/file.js',
  },
};

多个入口的写法

module.exports = {
  entry: ['./src/file_1.js', './src/file_2.js'],
  output: {
    filename: 'bundle.js',
  },
};

module.exports = {
  entry: {
    app: './src/app.js',
    adminApp: './src/adminApp.js',
  },
};

可配置的对象属性

  • dependOn: 当前入口所依赖的入口。它们必须在该入口被加载前被加载。

  • filename: 指定要输出的文件名称。

  • import: 启动时需加载的模块。

  • library: 指定 library 选项,为当前 entry 构建一个 library。

  • runtime: 运行时 chunk 的名字。如果设置了,就会创建一个新的运行时 chunk。在 webpack 5.43.0 之后可将其设为 false 以避免一个新的运行时 chunk。

  • publicPath: 当该入口的输出文件在浏览器中被引用时,为它们指定一个公共 URL 地址。请查看 output.publicPath。

需要注意的是

  • runtime 和 dependOn 不应在同一个入口上同时使用

  • 确保 runtime 不能指向已存在的入口名称

  • dependOn 不能是循环引用的

使用场景

1.分离 app(应用程序) 和 vendor(第三方库) 入口

module.exports = {
  entry: {
    main: './src/app.js',
    vendor: './src/vendor.js',
  },
};

这样你就可以在 vendor.js 中存入未做修改的必要 library 或文件(例如 Bootstrap, jQuery, 图片等),然后将它们打包在一起成为单独的 chunk。内容哈希保持不变,这使浏览器可以独立地缓存它们,从而减少了加载时间。

2.多页面应用程序

module.exports = {
  entry: {
    pageOne: './src/pageOne/index.js',
    pageTwo: './src/pageTwo/index.js',
    pageThree: './src/pageThree/index.js',
  },
};

在多页面应用程序中,server 会拉取一个新的 HTML 文档给你的客户端。页面重新加载此新文档,并且资源被重新下载。然而,这给了我们特殊的机会去做很多事,例如使用 optimization.splitChunks 为页面间共享的应用程序代码创建 bundle。由于入口起点数量的增多,多页应用能够复用多个入口起点之间的大量代码/模块,从而可以极大地从这些技术中受益。

2.output(输出)

有入口就有输出,output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。主要输出文件的默认值是 ./dist/main.js,其他生成文件默认放置在 ./dist 文件夹中。

即使可以存在多个 entry 起点,但只能指定一个 output 配置。

在 webpack 配置中,output 属性的最低要求是,将它的值设置为一个对象,然后为将输出文件的文件名配置为一个 output.filename

module.exports = {
  output: {
    filename: 'bundle.js',
  },
};

多个入口起点

如果配置中创建出多于一个 "chunk"(例如,使用多个入口起点或使用像CommonsChunkPlugin 这样的插件),则应该使用 占位符(substitutions) 来确保每个文件具有唯一的名称

module.exports = {
  entry: {
    app: './src/app.js',
    search: './src/search.js',
  },
  output: {
    filename: '[name].js',
    path: __dirname + '/dist',
  },
};

// 写入到硬盘:./dist/app.js, ./dist/search.js

高级进阶

对资源使用 CDN 和 hash 的复杂示例

module.exports = {
  //...
  output: {
    path: '/home/proj/cdn/assets/[fullhash]',
    publicPath: 'https://cdn.example.com/assets/[fullhash]/',
  },
};

如果在编译时,不知道最终输出文件的 publicPath 是什么地址,则可以将其留空,并且在运行时通过入口起点文件中的 __webpack_public_path__ 动态设置。

__webpack_public_path__ = myRuntimePublicPath;

// 应用程序入口的其余部分

3.loader

webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效模块,以供应用程序使用,以及被添加到依赖图中

const path = require('path');

module.exports = {
  output: {
    filename: 'my-first-webpack.bundle.js',
  },
  module: {
    rules: [{ test: /\.txt$/, use: 'raw-loader' }],
  },
};

test 属性,识别出哪些文件会被转换,use 属性,定义出在进行转换时,应该使用哪个 loader。

4.plugin(插件)

插件可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量。

插件是 webpack 的 支柱 功能。webpack 自身也是构建于你在 webpack 配置中用到的相同的插件系统之上! 插件目的在于解决 loader 无法实现的其他事。

const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通过 npm 安装
const webpack = require('webpack'); // 用于访问内置插件

module.exports = {
  module: {
    rules: [{ test: /\.txt$/, use: 'raw-loader' }],
  },
  plugins: [new HtmlWebpackPlugin({ template: './src/index.html' })],
};

5.mode(模式)

通过选择 development, production 或 none 之中的一个,来设置 mode 参数,其默认值为 production

module.exports = {
  mode: 'production',
};

3.configuration(配置)

基本配置

const path = require('path');

module.exports = {
  mode: 'development',
  entry: './foo.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'foo.bundle.js',
  },
};

4.项目实践

首先将配置文件分成三部分,分别是webpack.dev.js(开发),webpack.prod.js(生产),webpack.config.js(公共)

webpack.dev.js

const webpack = require('webpack');
const webpackMerge = require('webpack-merge');
const common = require('./webpack.config.js');
const path = require('path');

module.exports = webpackMerge.merge(common, {    mode: 'development', 
    entry: {
        app: './src/index.js' 
    },
    output: {
        path: path.resolve(__dirname, 'dev'), 
        filename: 'app.js', 
        publicPath: '/'  
    },
    devServer: {
        contentBase: './dev',
        historyApiFallback: {
            index: 'index.html'
        },
        hot: true,
        inline: true,
        port: 8888
    },
    plugins: [
        new webpack.DefinePlugin({
            "NODE_ENV": JSON.stringify('development')
        }),
        new webpack.HotModuleReplacementPlugin()
    ]
});

webpack.prod.js

const webpack = require('webpack');
const {merge} = require('webpack-merge');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const common = require('./webpack.config.js');
const path = require('path');

module.exports = merge(common, {
    devtool: 'source-map',
    mode: 'production',
    entry: {
        app: './src/index.js'
    },
    output: {
        path: path.resolve(__dirname, 'dist/'),
        filename: 'bundle/[name].[chunkhash].js',
        publicPath: '/'
    },
    optimization: {
        splitChunks: {
            chunks: "all",
        }
    },
    plugins: [
        new webpack.DefinePlugin({
            "NODE_ENV": JSON.stringify('production')
        }),
        new CleanWebpackPlugin({
                cleanOnceBeforeBuildPatterns: [
                    'bundle/*',
                ]
            }),
        new HtmlWebpackPlugin({
            title: '',
            inject: true,
            template: 'dev/index.html',
            filename: 'index.html'
        }),
    ]
});

webpack.config.js 

const path = require('path');
module.exports = {
    module: {
        rules: [
            {
                test: /\.(js)$/,
                exclude: /(node_modules|bower_components)/,
                loader: 'babel-loader',
                options: {
                    presets: ['@babel/preset-env', '@babel/preset-react'],
                    plugins: [
                        '@babel/plugin-transform-runtime',
                        '@babel/plugin-proposal-class-properties',
                        '@babel/plugin-transform-named-capturing-groups-regex'
                    ]
                },
            },
            {
                test: /\.(css)$/,
                use: ['style-loader', 'css-loader']
            },
            {
                test: /\.(scss)$/,
                use: ['style-loader', 'css-loader', 'sass-loader']
            },
            {
                test: /\.(less)$/,
                use: ['style-loader', 'css-loader', 'less-loader']
            },
            {
                test: /\.(png|jpg|gif)$/,
                use: ['file-loader']
            },
            {
                test: /\.(html)$/,
                use: ['html-loader']
            }
        ]
    },
};

现在的版本不代表最终上线版本,后面会根据具体情况增减,遇到不会的,百度找找方案,学习一下,毕竟是个工具。

参考资料:

zhuanlan.zhihu.com/p/363928061