loader
| loader | 解决问题 |
|---|---|
| babel-loader | 把ES6、React转换成ES5 |
| css-loader | 加载CSS,支持模块化、压缩、文件导入等特性 |
| eslint-loader | 通过es lint检查js代码 |
| file-loader | 把文件输出到一个文件夹中,在代码中通过相对URL去引用输出的文件 |
| url-loader | 和file loader类似,但是能在文件很小的情况下,以base64的方式把文件内容注入到代码中去 |
| sass-loader | 把sass/scss文件编译成CSS |
| postcss-loader | 使用post CSS处理CSS |
| css-loader | 主要处理background url还有@import这些语法。让Webpack能够正确的对其路径进行模块化处理 |
| style-loader | 把CSS代码注入到js中,通过DOM操作去加载CSS |
plugin
| 插件 | 解决问题 |
|---|---|
| case-sensitive-paths-webpack-plugin | 如果路径有误会直接报错 |
| terser-webpack-plugin | 使用terser来压缩JS |
| pnp-webpack-plugin | |
| html-webpack-plugin | 自动生成带有入口文件引用的index.html |
| webpack-manifest-plugin | 生产资产的显示清单文件 |
| optimize-css-assets-webpack-plugin | 用于优化或者压缩css文件 |
| mini-css-extract-plugin | 将css提取为独立的文件插件,对每个包含css的js文件都会创建一个css文件 |
| DefinePlugin | 创建一个在编译时可配置的全局变量,如果你定义了一个全局变量 |
| HotModuleReplacementPlugin | 启动模块热替换 |
| ModuleNotFoundPlugin | 找不到模块的时候提供一些信息 |
| ModuleScopePlugin | 如果引用了src目录外的文件报警告 |
下面是React官方webpack.config.js的配置文件,我们可以学习参考。
webpack.config.js
'use strict';
const webpack = require('webpack');
const paths = require("./paths");
const CaseSensitivePathsPlugin = require("case-sensitive-paths-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const PnpWebpackPlugin = require("pnp-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ManifestPlugin = require("webpack-manifest-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const InlineChunkHtmlPlugin = require("react-dev-utils/InlineChunkHtmlPlugin");
const WatchMissingNodeModulesPlugin = require("react-dev-utils/WatchMissingNodeModulesPlugin");
const ModuleScopePlugin = require("react-dev-utils/ModuleScopePlugin");
const InterpolateHtmlPlugin = require("react-dev-utils/InterpolateHtmlPlugin");
const ModuleNotFoundPlugin = require("react-dev-utils/ModuleNotFoundPlugin");
const getClientEnvironment = require("./env");
const cssRegex = /\.css$/;
const sassRegex = /\.(scss|sass)$/;
// webpackEnv在执行npm xxx命令时会传入,如 "build": "cross-env key=value webpack --env=production",
module.exports = function (webpackEnv) {
console.log("webpackEnv", webpackEnv); //webpackEnv production
//开发环境
const isEnvDevelopment = webpackEnv === "development";//false
//生产环境
const isEnvProduction = webpackEnv === "production";//true
//set GENERATE_SOURCEMAP=false 是否在生产环境下生成sourcemap文件
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== "false";
//是否把运行时的runtime内置到html中
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== "false";
console.log("shouldInlineRuntimeChunk", shouldInlineRuntimeChunk);
// %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
//忽略结束的/ 把环境变量中的变量注入到当前应用中来
const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1));
console.log("env", env);
const getStyleLoaders = (cssOptions, preProcessor) => {
const loaders = [
isEnvDevelopment && require.resolve("style-loader"), // 把css样式变成一个style标签插入到页面中
isEnvProduction && {
loader: MiniCssExtractPlugin.loader, // 把css放到一个单独的文件中
},
{
loader: require.resolve("css-loader"),
options: cssOptions,
},
{
loader: require.resolve("postcss-loader"),
},
].filter(Boolean);
if (preProcessor) {
loaders.push(
{
loader: require.resolve("resolve-url-loader"),
},
{
loader: require.resolve(preProcessor),
options: {
sourceMap: true,
},
}
);
}
return loaders;
};
return {
mode: isEnvProduction ? "production" : "development",
devtool: isEnvProduction
? shouldUseSourceMap
? "source-map"
: false
: isEnvDevelopment && "cheap-module-source-map",
entry: [
isEnvDevelopment &&
require.resolve("react-dev-utils/webpackHotDevClient"), // 热更新客户端,如果webpack要支持热更新,需要在客户端注入一个热更新代码,跟服务器进行一个连接,如果服务端代码变化后会通知客户端更新
paths.appIndexJs,
].filter(Boolean),
output: {
path: isEnvProduction ? paths.appBuild : undefined, //输出的目标路径
//一个main bundle一个文件,每个异步代码块也对应一个文件 ,在生产环境中,并不产出真正的文件
filename: isEnvProduction
? "static/js/[name].[contenthash:8].js"
: "static/js/bundle.js",
//如果使用了代码分割的话,这里有额外的JS代码块文件
chunkFilename: isEnvProduction
? "static/js/[name].[contenthash:8].chunk.js"
: "static/js/[name].chunk.js",
//打包后的文件的访问路径
publicPath: paths.publicUrlOrPath,
},
optimization: {
minimize: isEnvProduction,//生产环境要压缩 开发环境不压缩
minimizer: [
//压缩JS
new TerserPlugin({}),
//压缩CSS
new OptimizeCSSAssetsPlugin({}),
],
//自动分割第三方模块和公共模块
splitChunks: {
chunks: "all",
name: false,
},
//为了长期缓存保持运行时代码块是单独的文件
runtimeChunk: {
name: (entrypoint) => `runtime-${entrypoint.name}`,
},
},
resolve: {
//设置modules的目录
modules: ["node_modules", paths.appNodeModules],
//指定扩展名
extensions: paths.moduleFileExtensions.map((ext) => `.${ext}`),
alias: {
//设置别名
"react-native": "react-native-web",
},
plugins: [
//PnpWebpackPlugin,
//防止用户引用在src或者node_modules之外的文件
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
],
},
resolveLoader: {
//plugins: [PnpWebpackPlugin.moduleLoader(module)],
},
module: {
rules: [
//在babel处理之前执行linter
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
enforce: "pre",
use: [
{
loader: require.resolve("eslint-loader"),
},
],
include: paths.appSrc,
},
{
//OneOf会遍历接下来的loader直到找一个匹配要求的,如果没有匹配的会走file-loader
oneOf: [
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
loader: require.resolve("url-loader"),
},
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
include: paths.appSrc,
loader: require.resolve("babel-loader"),
},
{
test: cssRegex,
//用于配置css-loader,作用于@import资源之前有多少个loader
//0=>无(默认) 1=>postcss-loader 2 postcss-loader sass-loader
use: getStyleLoaders({ importLoaders: 1 }),
},
{
test: sassRegex,
//postcss-loader sass-loader
use: getStyleLoaders({ importLoaders: 3 }, "sass-loader"),
},
{
loader: require.resolve("file-loader"),
exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
options: {
name: "static/media/[name].[hash:8].[ext]",
},
},
],
},
],
},
plugins: [
//使用插入的script标签生成一个index.html插件
new HtmlWebpackPlugin({
inject: true,
template: paths.appHtml,
}),
//把运行时代码(webapck工作时需要用到的代码)插入到html里,这样可以节约一个请求
isEnvProduction &&
shouldInlineRuntimeChunk &&
new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]),
//保证在index.html中获取到环境变量public URL可以通过%PUBLIC_URL%获取
new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
//模块找不到的时候提供一些必要上下文信息
new ModuleNotFoundPlugin(paths.appPath),
//保证在JS中获取到环境变量if (process.env.NODE_ENV === 'production') { ... }
new webpack.DefinePlugin(env.stringified),
//模块热更新插件
isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),
//当你大小写拼错的时候进行提示
isEnvDevelopment && new CaseSensitivePathsPlugin(),
//重新安装模块后不用重新启动开发服务器
isEnvDevelopment &&
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
//提取CSS
isEnvProduction &&
new MiniCssExtractPlugin({
filename: "static/css/[name].[contenthash:8].css",
chunkFilename: "static/css/[name].[contenthash:8].chunk.css",
}),
//生成一个manifest文件
new ManifestPlugin({
fileName: "asset-manifest.json",
publicPath: paths.publicUrlOrPath,
generate: (seed, files, entrypoints) => {
const manifestFiles = files.reduce((manifest, file) => {
manifest[file.name] = file.path;
return manifest;
}, seed);
const entrypointFiles = entrypoints.main.filter(
(fileName) => !fileName.endsWith(".map")
);
return {
files: manifestFiles,
entrypoints: entrypointFiles,
};
},
}),
].filter(Boolean),
};
};
paths.js
'use strict';
const path = require('path');
const fs = require('fs');
//当前的工作目录
const appDirectory = fs.realpathSync(process.cwd());
//从相对路径中解析绝对路径
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
//获取PublicUrlOrPath 如当前项目在package.json文件中配置"homepage": "/static/"
const publicUrlOrPath = require(resolveApp("package.json")).homepage || process.env.PUBLIC_URL || "";
//默认的模块扩展名,这里列举的是常用的,不是全部的,如我们在引入模块可能不会写文件名后缀,它会在这里面依次找这些后缀的文件
const moduleFileExtensions = [
'js',
'ts',
'tsx',
'json',
'jsx',
];
//解析模块路径 filePath指的是文件路径 如'./title.js' resolveFn函数的作用是从相对路径得到绝对路径
const resolveModule = (resolveFn, filePath) => {
//js
const extension = moduleFileExtensions.find(extension =>
fs.existsSync(resolveFn(`${filePath}.${extension}`))
);
if (extension) {
return resolveFn(`${filePath}.${extension}`);//./title.js
}
return resolveFn(`${filePath}.js`);//如果没有,默认是.js文件
};
module.exports = {
// 环境变量有两种方式:
// 1. 新建个.evn文件,将环境变量写到文件中
// 2. 在终端使用 set xxx = xxx 来设置环境变量
// 一般我们使用.evn文件的方式,方便、快捷、便于管理
dotenv: resolveApp('.env'),//客户端环境变量的文件名路径
appPath: resolveApp('.'),//当前工作路径
appBuild: resolveApp('build'),//输出的build目标路径
appPublic: resolveApp('public'),//public目录
appHtml: resolveApp('public/index.html'),//html文件绝对路径
appIndexJs: resolveModule(resolveApp, 'src/index'),//入口文件
appPackageJson: resolveApp('package.json'),//package.json文件路径
appSrc: resolveApp('src'),//src路径
appTsConfig: resolveApp('tsconfig.json'),
appJsConfig: resolveApp('jsconfig.json'),
appNodeModules: resolveApp('node_modules'),
publicUrlOrPath,
};
module.exports.moduleFileExtensions = moduleFileExtensions;
env.js
'use strict';
const fs = require('fs');
const path = require('path');
const paths = require('./paths');
//一般可能是production或者development set NODE_ENV=development
const NODE_ENV = process.env.NODE_ENV;
//环境变量的文件路径
const dotenvFiles = [
`${paths.dotenv}.${NODE_ENV}.local`, // .env.development.local
`${paths.dotenv}.${NODE_ENV}`, // .env.development
//在测试环境下不要包括.env.local
NODE_ENV !== 'test' && `${paths.dotenv}.local`, // .env.local
paths.dotenv,//.env
].filter(Boolean);
//从.env*文件中加载环境变量,供程序读取如:process.env.PUBLIC_URL=//static2
process.env.username;
dotenvFiles.forEach(dotenvFile => {
if (fs.existsSync(dotenvFile)) {
require('dotenv-expand')(
require('dotenv').config({
path: dotenvFile,
})
);
}
});
//支持通过NODE_PATH加载解析模块: set NODE_PATH=modules或者extraModules
const appDirectory = fs.realpathSync(process.cwd());
//配置node_modules
process.env.NODE_PATH = (process.env.NODE_PATH || '')
.split(path.delimiter)
.filter(folder => folder && !path.isAbsolute(folder)) // 过滤绝对路径
.map(folder => path.resolve(appDirectory, folder)) // 把相对路径变成绝对路径
.join(path.delimiter);
//获取NODE_ENV and REACT_APP_*环境变量,并且准备通过DefinePlugin插入应用
//如:set REACT_APP_NAME=f
const REACT_APP = /^REACT_APP_/i;
function getClientEnvironment(publicUrl) {
const raw = Object.keys(process.env) // 拿到env里面所有的key
.filter((key) => REACT_APP.test(key)) // 找到以REACT_APP_开头的
.reduce(
(env, key) => {
env[key] = process.env[key];
return env;
},
{
//决定当前是否处于开发模式
NODE_ENV: process.env.NODE_ENV || "development",
//用来解析处于public下面的正确资源路径
PUBLIC_URL: publicUrl,
}
);
//把所有的值转成字符串以便在DefinePlugin中使用
const stringified = {
"process.env": Object.keys(raw).reduce((env, key) => {
env[key] = JSON.stringify(raw[key]);
return env;
}, {}),
};
return { raw, stringified };
}
module.exports = getClientEnvironment;