[Webpack] 核心概念、基础配置、常用loader和常用插件

1,688 阅读6分钟

Webpack 是一个JavaScript应用程序的打包模块化工具.Webpack里一切文件都是模块,处理应用程序时,它会递归构建程序中各个模块的依赖关系图, 最后将这些模块打包成一个或者多个bundle

核心概念

入口(enrty)

入口起点指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。

出口(output)

output 属性告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist。基本上,整个应用程序结构,都会被编译到你指定的输出路径的文件夹中。可以通过在配置中指定一个 output 字段,来配置这些处理过程。

loader

loade 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后就可以利用 webpack 的打包能力,对它们进行处理。本质上,webpack loader 将所有类型的文件,转换为应用程序的依赖图(和最终的 bundle)可以直接引用的模块。

插件(plugins)

loader 被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。

模式

通过选择 developmentproduction 之中的一个,来设置 mode 参数,可以启用相应模式下的 webpack 内置的优化。

基础配置

可以通过一个JavaScript文件导出Webpack的配置:

module.exports = {
  mode: "development",	// 模式
  entry: {},	// 入口
  output: {},	// 输出
  resolve: {},	// 解析
  module: {},	// 模块
  plugins: [],	// 插件
}

mode

mode的取值有developmentproduction。通过设置mode,webpack在打包时会使用相应模式的内置优化,并process.env.NODE_ENV设置为对应的值。

entry

entry是应用的入口,可以传入一个字符串、数组或者对象。如果传入一个字符串或字符串数组,chunk 会被命名为 main。如果传入一个对象,则每个键(key)会是 chunk 的名称,该值描述了 chunk 的入口起点。

module.exports = {
    // ...
    entry: {
        app: "./src/index.js"
    }
}

output

output 可以控制 webpack 如何向硬盘写入编译文件。即使可以存在多个入口起点,但只指定一个输出配置。

  • output.path: 指定打包后内容的输出目录

  • output.filename: 配置每个输出bundle的名称,可以使用模板字符串配置输出。

    模板 描述
    [hash] 模块标识符的hash
    [chunkhash] chunk内容的hash
    [name] 模块名称
    [id] 模块标识符
    [query] 模块的query
  • output.chunkFilename:配置非入口chunk文件的名称,默认[id].js,与filename选项一致可以使用模板字符串。

  • output.publicPath:指定在浏览器中所引用的此输出目录对应的公开 URL

module.export = {
    // ...
    output: {
        path: "./dist",
        publicPath: "https://example.com",
        filename: "[name].[chunkhash].js",
        chunkFilename:  "[name].[chunkhash].js"
    }
}

resolve

resolve设置模块如何被解析。

  • resolve.alias:设置导入模块时的别名
  • resolve.modules:设置模块解析时搜索的目录
module.exports = {
    // ...
    resolve: {
        alias: {
            "@src": "./src/"
        },
        modules: ["./node_modules"]
    }
}

module

module设置webpack如何处理不同类型的模块。

  • module.rules:配置规则数组。在创建模块时,匹配对应的规则并应用loader处理模块。

规则(Rule)的配置:

  • rule.test:匹配模块文件路径
  • rule.include:匹配目录下的文件
  • rule.exclude:匹配不在目录下的文件
  • rule.use:使用loaderloader应用顺序为从右到左
module.exports = {
    // ...
    rules: [{
        test: /.js$/,
        include: ['./src'],
        use: ["babel-loader"]
    }]
}

plugins

plugins设置打包过程中使用的插件。

module.exports = {
    // ...
    plugins: []
}

devtool

设置source maps如何生成,开发时可以将此选项设置为cheap-module-eval-source-map

module.exports = {
    // ...
    devtool: "cheap-module-eval-source-map"
}

devServer

设置webpack-dev-server使用时的选项,可以配置监听端口、代理等。

const path = require("path")
module.exports = {
    // ...
    devServer: {
        host: "0.0.0.0",
        contentBase: path.resolve(__dirname, "build"),
		hot: true,	// 开启模块热替换
		historyApiFallback: true,  // 使用index.html页面来代替404响应,使用HTML5 History API时可将该选项设置为true,例如react-router
		proxy: {
            '/api': 'http://127.0.0.1:3000'
        }
    }
}

常用loader

CSS相关

用于解析CSSloader一般有:style-loadercss-loaderpostcss-loaderless-loadersass-loader

  • style-loader:通过注入<style>标签将CSS添加到DOM
  • css-loader:解释@importurl(),会 import/require() 后再解析它们。css-loader前应用了loader需要指定importLoaders选项
  • postcss-loader:应用postcss
  • less-loader:解析less
  • sass-loader:解析sass
module.exports = {
    // ...
    module: {
        rules: [{
            test: /.less$/,
            include: ["./src"],
            use: [
                "style-loader",
                {
                    loader: "css-loader",
                    options: {
                        importLoaders: 2
                    }
                },
                "postcss-loader",
                "less-loader"
            ]
        }]
    }
}

JavaScript相关

  • babel-loaderbabel-loader基于babel,用于解析JavaScript文件。babel有丰富的预设和插件,babel的配置可以直接写到options里或者单独写道配置文件里。

几个babel常用的预设和插件:

  • @babel/preset-env:用于转换最新的JavaScript代码、为低版本浏览器提供polyfill
  • @babel/preset-react:用于解析jsx内容,该预设包含了@babel/plugin-syntax-jsx@babel/plugin-transform-react-jsx@babel/plugin-transform-react-display-name三个插件。当指定了development选项时,还额外包含@babel/plugin-transform-react-jsx-self@babel/plugin-transform-react-jsx-source两个插件
  • @babel/preset-typescript:用于解析typescript内容,该预设仅包含@babel/plugin-transform-typescript插件。在解析typescript内容时,该预设会直接把typescript相关的内容去掉
  • @babel/plugin-proposal-class-properties:用于解析类属性的插件
  • @babel/plugin-proposal-object-rest-spread:用于解析析构对象的插件
  • @babel/plugin-transform-runtime:用于复用babel内置的helper代码从而减小代码体积

babel常用的预设有preset-envpreset-reactpreset

module.export = {
    // ...
    module: {
        rules: [{
            test: /.js$/,
            include: ["./src/"],
            use: [{
                loader: "babel-loader",
                options: {
                    presets: [
                       "@babel/preset-env",
                        "@babel/preset-react",
                        "@babel/preset-typescript",
                    ],
                    plugins: [
                        "@babel/plugin-proposal-class-properties",
   						 "@babel/plugin-proposal-object-rest-spread",
                    ]
                }
            }]
        }]
    }
}

其他资源

除了CSSJavaScript资源外,项目里还有其他的资源,例如图片、字体等。对于这类资源,可以通过file-loaderurl-loader解析。

  • file-loader:告诉Webpack引用的模块是一个文件,并返回其打包后的url
  • url-loader:作用与file-loader类似,但当文件大小(单位 byte)低于指定的限制时,可以返回一个 DataURL。
module.exports = {
    // ...
    module: {
        rules: [{
        	test: /\.(png|jpg|gif)$/,
        	use: [{
            	loader: 'url-loader',
            	options: {
              		limit: 8192
            	}
          	}]
        }]
    }
}

常用插件

copy-webpack-plugin

copy-webpack-plugin用于打包时复制文件。

const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
    // ...
    plugins: [
        new CopyWebpackPlugin({
        	{
        		from: "./public",
        		to: "./build/"
      		}
        })
    ]
} 

html-webpack-plugin

生成一个 HTML5 文件,包括使用 script 标签的 body 中的所有 webpack 包。

const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
	// ...
	plugins: [
        new HtmlWebpackPlugin({
            template: "./public/index.html",
      		chunks: ["app"]
        })
    ]
};

add-asset-html-webpack-plugin

插入一个JavaScript或者CSS资源到html-webpack-plugin生产的html页面中。使用DLL时,可以通过该插件将生成的JavaScript文件插入到页面中。

const HtmlWebpackPlugin = require('html-webpack-plugin');
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
const path = require("path")
module.exports = {
	// ...
	plugins: [
		new HtmlWebpackPlugin(),
		new AddAssetHtmlPlugin({
            filepath: path.resolve(__dirname, './dlls/*.dll.js'),
      		outputPath: "dlls",
      		publicPath: "/dlls"
        })
    ]
};

clean-webpack-plugin

clean-webpack-plugin可以清理文件,可以用于打包时清理之前打包的文件。

const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
	// ...
	plugins: [
		new CleanWebpackPlugin()
    ]
};

mini-css-extract-plugin

将CSS提取到单独的文件中,为每个包含CSS的JS文件创建一个CSS文件,同时该插件支持CSS和SourceMap的按需加载。使用该插件时,style-loader需要替换为该插件提供的loader

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
	// ...
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    MiniCssExtractPlugin.loader,	// 需要替换 style-loader
					{
						loader: "css-loader",
						options: {
							importLoaders: 1
						}
					}
				]
            }
        ]
    },
	plugins: [
		new MiniCssExtractPlugin({
			filename: "static/css/[name].[chunkhash:8].css",
			chunkFilename: "static/css/[name].[chunkhash:8].chunk.css"
		})
    ]
};

webpack.HotModuleReplacementPlugin

模块热替换插件,开启HMR。可以和webpack.NamedModulesPlugin搭配使用,显示模块的相对路径,建议用于开发环境。

const webpack = require('webpack');
module.exports = {
	// ...
	plugins: [
        new webpack.NamedModulesPlugin(),
        new webpack.HotModuleReplacementPlugin(),
    ]
};

webpack.DefinePlugin

创建一个在编译时可以配置的全局常量。

const webpack = require('webpack');
module.exports = {
	// ...
	plugins: [
        new webpack.DefinePlugin({
            VERSION: JSON.stringify("0.0.1")
        })
    ]
};

因为这个插件直接执行文本替换,给定的值必须包含字符串本身内的实际引号。通常,有两种方式来达到这个效果,使用'"production"', 或者使用 JSON.stringify('production')

webpack.DllPluginwebpack.DllReferencePlugin

DllPlugin和DllReferencePlugin提供了一种拆分包的方法,可以极大地提高构建时间性能。

// 制作dll
const webpack = require("webpack");
const path = require("path");

module.exports = {
	mode: "production",
	entry: {
        react: ["react", "react-dom"],
        antd: ["antd"],
        utils: ["lodash", "moment"]
    },
    output: {
		path: "./dlls",
		filename: "[name].dll.js",
		library: "[name]_[hash]"
	},
	resolve: {
		extensions: [".js", ".jsx"],
		modules: ["./node_modules"]
	},
	plugins: [
		new webpack.DllPlugin({
			path: path.resolve(__dirname, "./dlls/[name].manifest.json"),
			name: "[name]_[hash]"
		})
	]
};
// 使用dll
const webpack = require("webpack");
const path = require("path");

module.exports = {
	// ...
	plugins: [
		new webpack.DllReferencePlugin({
			manifest: path.resolve(__dirname, "./dlls/react.manifest.json"),
		}),
        new webpack.DllReferencePlugin({
			manifest: path.resolve(__dirname, "./dlls/antd.manifest.json"),
		}),
        new webpack.DllReferencePlugin({
			manifest: path.resolve(__dirname, "./dlls/utils.manifest.json"),
		}),
		new AddAssetHtmlWebpackPlugin({
			filepath: path.resolves(__dirname,  './dlls/*.dll.js'),
			outputPath: "dlls",
			publicPath: "/dlls"
		})
	]
};