Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/sobird/webpack-tutorial

Webpack Tutorial
https://github.com/sobird/webpack-tutorial

Last synced: 6 days ago
JSON representation

Webpack Tutorial

Awesome Lists containing this project

README

        

= webpack5 配置
:toc: auto
:toc-title:
:toclevels: 4

本文档从零开始配置 `react + typescript` 项目。

== 安装webpack
* *webpack* webpack 打包工具
* *webpack-cli* webpack 命令行接口
* *webpack-dev-server* 在本地启动一个http服务,运行应用

```sh
yarn add -D webpack webpack-cli webpack-dev-server
```

## webpack.config.js
在项目根目录创建 `webpack.config.js` 配置文件,基本结构如下:

```js
const isProduction = process.env.NODE_ENV === 'production';

const config = {
// webpack config
};

module.exports = () => {
if (isProduction) {
config.mode = 'production';

} else {
config.mode = 'development';
}
return config;
}
```

## 公共配置
配置 `entry`,`output`,`devtool`, `devServer`,`resolve`
```js
{
devtool: isProduction ? false : 'inline-source-map',
entry: {
app: [
'./src/index.tsx',
],
},
output: {
path: path.resolve(__dirname, 'dist'),
// pathinfo: true,
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].chunk.js',
publicPath: '/',
},
devServer: {
open: true,
host: 'localhost',
port: 3000,
hot: true, // 热更新
historyApiFallback: true,
},
resolve: {
extensions: ['.tsx', '.ts', '.jsx', '.js', '...'],
alias: {
'@': path.join(__dirname, 'src'),
},
}
}
```
### 热更新
在入口文件 `src/index.js` 尾部添加:

```ts
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(


,
)

// 支持热更新功能
module?.hot?.accept();
```

为防止 ts 报错,安装 `@types/webpack-env`

```sh
yarn add -D @types/webpack-env
```

## scripts
配置 `package.json` 中的 `scripts` 字段,添加start、build、lint等命令

```json
"scripts": {
"start": "NODE_ENV=development webpack serve",
"build": "webpack --mode=production --node-env=production",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
},
```

## plugin
### html-webpack-plugin
创建应用入口html,同时自动添加 `bundle.js` 文件,通过 `yarn add -D html-webpack-plugin` 命令安装,基本配置如下:

```js
const HtmlWebpackPlugin = require('html-webpack-plugin');

const config = {
plugins: [
new HtmlWebpackPlugin({
template: path.resolve('public/index.html'),
filename: 'index.html',
minify: true,
inject: true,
title: 'Webpack App',
}),
]
}
```

### dotenv-webpack
为项目添加 `process.env` 环境变量,通过 `yarn add -D dotenv-webpack` 命令安装,基本配置如下:

```js
const Dotenv = require('dotenv-webpack');

const config = {
plugins: [
new Dotenv({
path: path.join(__dirname, `.env.${process.env.NODE_ENV}`),
safe: true,
// hide any errors
silent: true,
// load all the predefined 'process.env' variables which will trump anything local per dotenv specs.
systemvars: true,
// Allows your variables to be "expanded" for reusability within your .env file.
expand: true,
// allow empty variables (e.g. `FOO=`) (treat it as empty string, rather than missing)
allowEmptyValues: true,
// load '.env.defaults' as the default values if empty.
defaults: path.join(__dirname, '.env.defaults'),
}),
]
}
```

### clean-webpack-plugin
webpack 5.20.0+ 版本中通过如下配置,可替换 clean-webpack-plugin 插件功能。

```
module.exports = {
//...
output: {
clean: true, // 在生成文件之前清空 output 目录
},
};
```

### mini-css-extract-plugin
将CSS提取到单独的CSS文件中,通过 `yarn add -D mini-css-extract-plugin` 安装,基本配置如下:

```js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
...
module.exports = () => {
if (isProduction) {
config.mode = 'production';

config.plugins.push(new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
chunkFilename: '[id].[contenthash].css',
}));
} else {
config.mode = 'development';
}
return config;
};
```

### css-minimizer-webpack-plugin
css压缩优化,通过 `yarn add -D css-minimizer-webpack-plugin` 安装,基本配置如下:

```js
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

const config = {
optimization: {
minimizer: [
// 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`),将下一行取消注释
// `...`,
new CssMinimizerPlugin(),
],
},
};
```

### terser-webpack-plugin
`terser-webpack-plugin` 内部封装了 `terser` 库,用于处理 `js` 的压缩和混淆,通过 `webpack plugin` 的方式对代码进行处理。

`webpack v5` 开箱即带有最新版本的 `terser-webpack-plugin`。如果你使用的是 `webpack v5` 或更高版本,同时希望自定义配置,那么仍需要安装 `terser-webpack-plugin`。如果使用 `webpack v4`,则必须安装 `terser-webpack-plugin v4` 的版本。

```js
const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
optimization: {
// 告知 webpack 使用 TerserPlugin 或其它在 optimization.minimizer定义的插件压缩 bundle。
minimize: true,
// 允许你通过提供一个或多个定制过的 TerserPlugin 实例,覆盖默认压缩工具(minimizer)。
minimizer: [new TerserPlugin()],
},
};
```

### webpack.ProgressPlugin
在打包构建过程中输出当前的进度信息

```js
const webpack = require('webpack');

const config = {
plugins: [
new webpack.ProgressPlugin({
activeModules: false,
entries: true,
handler(percentage, message, ...args) {
// custom logic
},
modules: true,
modulesCount: 5000,
profile: false,
dependencies: true,
dependenciesCount: 10000,
percentBy: null,
})
]
}
```

### purgecss-webpack-plugin
去除无用样式,通过 `yarn add -D purgecss-webpack-plugin`安装,基本配置如下:

```js
const glob = require('glob');
const { PurgeCSSPlugin } = require('purgecss-webpack-plugin');

const config = {
plugins: [
new PurgeCSSPlugin({
paths: glob.sync(`${path.resolve(__dirname, './src')}/**/*.{tsx,scss,less,css}`, { nodir: true }),
whitelist: ['html', 'body']
}),
]
}
```

### webpack-bundle-analyzer
打包分析工具,通过 `yarn add -D webpack-bundle-analyzer`安装,基本配置如下:

```js
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');

const config = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
analyzerHost: '127.0.0.1',
analyzerPort: 8888,
}),
]
}
```

### SourceMapDevToolPlugin
本插件实现了对 source map 生成内容进行更细粒度的控制。它也可以根据 devtool 配置选项的某些设置来自动启用。

```js
module.exports = (conf) => {
console.log('conf', conf, process.env.NODE_ENV)
if (isProduction) {
config.mode = 'production';

config.plugins.push(new webpack.SourceMapDevToolPlugin({
test: /\.(tsx|jsx|js)$/,
filename: '[file].map',
publicPath: '/',
}));
} else {
config.mode = 'development';
}
return config;
}
```

### compression-webpack-plugin
gzip压缩静态资源

```js
const CompressionPlugin = require("compression-webpack-plugin");

module.exports = {
plugins: [new CompressionPlugin()],
};
```

## loader

### ts-loader
将 `TypeScript` 转化为 `JavaScript`,通过 `yarn add -D ts-loader` 安装,基本配置如下:

```js
const config = {
module: {
rules: [
{
// test: /\.ts(x?)$/,
test: /\.(ts|tsx)$/i,
use: [{
loader: 'ts-loader',
options: {
// 跳过ts类型检查
transpileOnly: true,
},
}],
exclude: ['/node_modules/'],
},
]
}
}
```

### esbuild-loader
`esbuild-loader` 是一个构建在 esbuild 上的 webpack loader,且可以替代 `babel-loader` 或 `ts-loader` 来提高构建速度

```js
const config = {
module: {
rules: [
{
// Match js, jsx, ts & tsx files
test: /\.[jt]sx?$/,
loader: 'esbuild-loader',
options: {
// JavaScript version to compile to
target: 'es2015'
}
},
],
},
}
```

`esbuild-loader` 可替换 TerserPlugin 和 CssMinimizerPlugin

```js
const { EsbuildPlugin } = require('esbuild-loader')

const config = {
optimization: {
minimizer: [
new EsbuildPlugin({
target: 'es2015' // Syntax to compile to (see options below for possible values)
})
]
},
}
```

### css

* *style-loader* 将js文件中引入的css插入到html模板文件
* *mini-css-extract-plugin* 和 `style-loader` 功能一样,只是打包后会单独生成 css 文件而非直接写在 html 文件中,用于生产环境,开发环境不需要另外生成文件
* *css-loader* 让js文件可以通过 `import` 或 `require` 等命令导入css代码
* *sass-loader* 将sass代码转换为css代码
* *less-loader* 将less代码转换为css代码
* *postcss-loader* 处理css代码,因为是处理css,所以postcss-loader要放在sass-loader等之后,可以在 `webpack.config.js` 中直接进行配置,或在 `postcss.config.js` 中进行配置,postcss-loader会自动加载该配置文件。
* *postcss-preset-env* 将最新的css语法转换为目标环境浏览器能够理解的语法,新版本已内置autoprefixer功能

*postcss.config.js*

```js
module.exports = {
plugins: [
[
"postcss-preset-env",
{
// Options
},
],
],
};
```

通过下面命令批量安装所需的css相关loader
```js
yarn add -D style-loader css-loader sass-loader less-loader postcss-loader postcss-preset-env postcss sass
```

css 相关loader配置如下:
```js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const isProduction = process.env.NODE_ENV === 'production';
const stylesHandler = isProduction ? MiniCssExtractPlugin.loader : 'style-loader';

const config = {
module: {
rules: [
{
test: /\.s[ac]ss$/i,
use: [stylesHandler, 'css-loader', 'postcss-loader', 'sass-loader'],
},
{
test: /\.less$/,
use: [stylesHandler, 'css-loader', 'postcss-loader',
{
loader: 'less-loader',
options: {
lessOptions: {
modifyVars: {
'primary-color': '#0080FF',
},
javascriptEnabled: true,
math: 'always',
},
},
},
],
},
{
test: /\.css$/i,
use: [stylesHandler, 'css-loader', 'postcss-loader'],
},
]
}
}
```

### https://webpack.docschina.org/guides/asset-modules/[asset module]
在 webpack 5 之前,通常使用:

* *raw-loader* 将文件导入为字符串
* *url-loader* 将文件作为 data URI 内联到 bundle 中
* *file-loader* 将文件发送到输出目录

```js
const config = {
module: {
rules: [
{
test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
type: 'asset',
},
]
}
}
```

## cache
缓存生成的 webpack 模块和 chunk,来改善构建速度。cache 会在开发 模式被设置成 type: 'memory' 而且在 生产 模式 中被禁用。

```js
const config = {
cache: {
type: 'filesystem',
buildDependencies: {
// This makes all dependencies of this file - build dependencies
config: [__filename],
// 默认情况下 webpack 与 loader 是构建依赖。
},
}
}
```

## externals
`externals` 配置项提供了阻止将某些 import 的包(package)打包到 `bundle` 中的功能,在运行时(runtime)再从外部获取这些扩展依赖(external dependencies)

## 编码风格
### EditorConfig

```sh
root = true

[*] # 匹配全部文件
charset = utf-8 # 设置字符集
indent_style = space # 缩进风格,可选 space、tab
indent_size = 2 # 缩进的空格数
end_of_line = lf # 结尾换行符,可选 lf、cr、crlf
insert_final_newline = true # 在文件结尾插入新行
trim_trailing_whitespace = true # 删除一行中的前后空格

[*.md]
trim_trailing_whitespace = false

[Makefile]
indent_style = tab
```

### Prettier
代码格式化工具,目前总共有23个配置项。通过 `yarn add -D prettier` 安装

在项目根目录下新建 `.prettierrc.js`

```js
module.exports = {
// 每行代码长度 默认80
printWidth: 100,
// 每个tab相当于多少个空格 默认2
tabWidth: 2,
// 是否使用tab进行缩进 默认false
useTabs: false,
// 声明结尾使用分号 默认true
semi: true,
// 使用单引号 默认false
singleQuote: true,
/**
* 对象属性的引号使用
*
* "as-needed" 仅在需要时在对象属性周围添加引号。
* "consistent" 如果对象中至少有一个属性需要引号,请引用所有属性。
* "preserve" 保留用户输入的情况
*/
quoteProps: 'as-needed',
// 在JSX中使用单引号而不是双引号,默认值为false
jsxSingleQuote: true,
// 在对象或数组最后一个元素后面是否加逗号(在ES5中加尾逗号)
trailingComma: 'es5',
// 字面量对象括号中的空格,默认值为true
bracketSpacing: true,
/**
* 多行JSX中的 > 放置在最后一行的结尾,而不是另起一行 默认值为false
*
* false:
*
* Click Here
*
*
* true:
*
* Click Here
*
*/
jsxBracketSameLine: false,
/**
* 箭头函数参数括号 默认avoid 可选 avoid always
* avoid 能省略括号的时候就省略 例如x => x
* always 总是有括号
*/
arrowParens: 'avoid',
// 格式化文件中某一段代码,默认格式化整个文件
rangeStart: 0,
rangeEnd: Infinity,
// 格式化的解析器,默认值为babylon(until v1.13.0)
parser: 'babylon',
/**
* 指定要使用的文件名,以推断要使用哪个解析器。
* 该选项仅在CLI和API中有用。在配置文件中使用它没有意义。
*/
filepath: '',
/**
* Prettier可以限制自己只格式化在文件顶部包含特殊注释(称为pragma)的文件。
* 这在逐渐将大型、未格式化的代码库转换为更漂亮的代码库时非常有用。
*
* @prettier
*/
requirePragma: false,

/**
* Prettier可以在文件顶部插入一个特殊的@format标记,指定该文件已使用Prettier进行格式化。
* 当与——require-pragma选项一起使用时,效果很好。
* 如果在文件的顶部已经有一个文档块,那么这个选项将添加一个带有@format标记的换行符。
*
* @prettier
*/
insertPragma: false,
/**
* "always" - Wrap prose if it exceeds the print width.
* "never" - Un-wrap each block of prose into one line.
* "preserve" - Do nothing, leave prose as-is. First available in v1.9.0
*/
proseWrap: 'preserve',

/**
* "css" - 遵守CSS display 属性的默认值
* "strict" - 空格被认为是敏感的
* "ignore" - 空格被认为是不敏感的
*/
htmlWhitespaceSensitivity: 'ignore',
// 是否缩进Vue文件中的脚本和样式标签 默认值为false
vueIndentScriptAndStyle: false,
/**
* 设置统一的行结尾样式(适用于v1.15.0+) 默认值为lf
*
* "lf" – 仅换行(\ n),在Linux和macOS以及git repos内部通用
* "crlf" - 回车符+换行符(\ r \ n),在Windows上很常见
* "cr" - 仅回车符(\ r),很少使用
* "auto" - 保持现有的行尾(通过查看第一行后的内容对一个文件中的混合值进行归一化)
*/
endOfLine: 'lf',
/**
* 控制Prettier是否格式化文件中嵌入的引用代码。
*
* "auto" - 如果pretty可以自动识别,则格式化嵌入的代码。
* "off" - 永远不要自动格式化嵌入代码。
*/
embeddedLanguageFormatting: 'auto',
// 在HTML、Vue和JSX中是否强制每行使用单个属性。默认值为false
singleAttributePerLine: false
};
```
如果配置了 `prettier` 注意配置规则与eslint保持一致,或者仅配置eslint规则即可。

## .browserslistrc
在不同的前端工具之间共用目标浏览器和 `Node` 版本的配置文件。它主要被以下工具使用:*Autoprefixer*,*Babel*,*post-preset-env*,*eslint-plugin-compat*,*stylelint-unsupported-browser-features*,*postcss-normalize* 。

在 `package.json` 中配置:

```json
{
"browserslist": [
"> 0.25%",
"not dead",
"ie 10",
"chrome 45",
"ios 9",
"android 4.4",
]
}

```

在 `.browserslistrc` 中配置:

```sh
# 默认值

> 0.5%
last 2 versions
Firefox ESR
not dead
```

## eslint (以配置airbnb规则为例)

```sh
# 快速初始化一个eslint配置
npm init @eslint/config
```

### 安装airbnb基础规则

```sh
npx install-peerdeps --dev eslint-config-airbnb
# 等同于运行下面命令
yarn add [email protected] eslint@^8.2.0 eslint-plugin-import@^2.25.3 eslint-plugin-jsx-a11y@^6.5.1 eslint-plugin-react@^7.28.0 eslint-plugin-react-hooks@^4.3.0 --dev
```

### 安装airbnb-typescript规则
```sh
yarn add eslint-config-airbnb-typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser --dev
```

* eslint
* eslint-config-airbnb
* eslint-plugin-import
* eslint-plugin-jsx-a11y
* eslint-plugin-react
* eslint-plugin-react-hooks
* @typescript-eslint/eslint-plugin
* @typescript-eslint/parser

在项目根目录下新建 `.eslintrc.cjs` 文件

```js
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
env: { browser: true, es2020: true, node: true, },
parser: '@typescript-eslint/parser',
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
plugins: ['react-refresh'],
rules: {
// 使用2个空格缩进
'indent': ['error', 2, { SwitchCase: 1 }],
},
}

```

当保存代码时,按照eslint设置的规则自动修复代码,配置如下:

```json
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
}
```