webpack基础配置
安装
mkdir webpack-demo
cd webpack-demo
yarn init
yarn add webpack webpack-cli -D
文件目录结构
webpack/
|- /src
|- index.js
|- index.html
|- package.json
默认文件配置
在文件根目录下创建:
touch webpack.config.js
复制代码
webpack.config.js
// webpack 配置文件
const path = require('path'); // node.js 的路径模块
module.exports = {
// entry: './src/index.js', // 入口文件(简写形式)
entry: {
main: './src/index.js',
},
output: {
path: path.resolve(__dirname, 'dist'), // 打包后的路径
filename: 'bundle.js', // 打包后的文件名
}
}
复制代码
以上是简单的打包配置,配置项包括入口文件、打包路径、打包文件名。
简化打包流程
在 package.json 中的 scripts 字段来编写运行脚本,通过脚本进行打包。
"scripts": {
"bundle": "webpack"
},
这个时候只需要运行 yarn build
就可打包了。
打包模式
// webpack.config.js
const path = require('path');
module.exports = {
mode: 'development', // 'development' | 'production'
// entry: './src/index.js',
entry: {
main: './src/index.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
}
可以配置为开发模式或者生产模式:
- 开发模式:本地环境
- 生产模式:线上环境(代码是压缩的)
具体有何不同,在打包后,打开 dist 文件夹下的 main.js,便可一目了然,此处不再赘述。
静态资源处理
- 样式文件(CSS)
- 图片(Images)
- 字体(Fonts)
- 数据文件(Data)
Asset Modules — 静态资源模块
根据 Webpack5 的文档,它简化了之前版本对于文件方面的配置,提供了 Asset Modules(静态资源模块),替代了之前常用的 raw-loader、url-loader、file-loader。
也就是说不用再安装这几个 loader 了,直接通过一行type: 'asset'
搞定,书写更加方便!(注意 module 中 type 的设置)
// webpack 配置文件
const path = require("path");
module.exports = {
mode: "production",
entry: {
main: "./src/index.js",
},
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
},
// *** 模块选项中匹配的文件会通过 loaders 来转换!
module: {
rules: [
// 图片文件
{
test: /\.(jpe?g|png|gif|svg)$/i,
type: "asset", // 一般会转换为 "asset/resource"
},
// 字体文件
{
test: /\.(otf|eot|woff2?|ttf|svg)$/i,
type: "asset", // 一般会转换为 "asset/inline"
},
// 数据文件
{
test: /\.(txt|xml)$/i,
type: "asset", // 一般会转换成 "asset/source"
},
],
},
};
最小化svg
yarn add mini-svg-data-uri -D
配置静态文件名
const path = require('path') // node.js 的路径模块
const miniSVGDataURI = require("mini-svg-data-uri"); // 最小化svg
module.exports = {
mode: 'development', // 'development' | 'production'
// entry: './src/index.js', // 入口文件(简写形式)
entry: {
main: './src/index.js',
},
output: {
path: path.resolve(__dirname, 'dist'), // 打包后的路径
filename: 'bundle.js', // 打包后的文件名
// 静态文件打包后的路径及文件名(默认是走全局的,如果有独立的设置就按照自己独立的设置来。)
assetModuleFilename: "assets/[name]_[hash][ext]",
},
// *** 模块选项中匹配的文件会通过 loaders 来转换!
module: {
rules: [
// 图片文件
{
test: /\.(jpe?g|png|gif|svg)$/i,
type: 'asset', // 一般会转换为 "asset/resource"
generator: {
filename: "images/[name]_[hash][ext]", // 独立的配置
},
parser: {
dataUrlCondition: {
maxSize: 8 * 1024 // 8kb (低于8kb都会压缩成 base64)
}
},
},
// 字体文件
{
test: /\.(otf|eot|woff2?|ttf|svg)$/i,
type: 'asset', // 一般会转换为 "asset/inline"
generator: {
filename: "fonts/[name]_[hash][ext]",
},
},
// 数据文件
{
test: /\.(txt|xml)$/i,
type: 'asset/source', // 一般会转换成 "asset/source"
},
// svg文件
{
test: /\.svg$/i,
type: "asset",
generator: {
dataUrl(content) {
content = content.toString();
return miniSVGDataURI(content);
},
},
parser: {
dataUrlCondition: {
maxSize: 2 * 1024 // 2kb (低于2kb都会压缩)
}
},
},
],
},
}
assetModuleFilename: 'assets/[name][ext]',
用于设置全局的静态文件路径及文件名。如果文件模块没有单独进行配置,就按照这个来设置文件名。
其中,[name] 表示原来的文件名,[hash] 表示散列值,[ext] 表示文件后缀。
样式文件的处理
yarn add css-loader style-loader -D
// css配置
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
yarn add sass-loader sass webpack -D
// ...
{
test: /\.s[ac]ss$/i,
use: [
// Creates `style` nodes from JS strings
"style-loader",
// Translates CSS into CommonJS
"css-loader",
// Compiles Sass to CSS
"sass-loader",
],
},
yarn add postcss-loader postcss -D
{
test: /\.s?css$/i,
use: ["style-loader", "css-loader", "sass-loader", "postcss-loader"],
},
注意,postcss-loader 要放在最后。
yarn add postcss-preset-env autoprefixer postcss-scss -D
{
test: /\.sc?ss$/i,
use: [
"style-loader",
{
loader: "css-loader",
options: {
importLoaders: 2,
// 0 => no loaders (default);
// 1 => postcss-loader;
// 2 => postcss-loader, sass-loader
modules: true, // 默认是 false ***
},
},
"sass-loader",
"postcss-loader",
],
},
开发环境配置
yarn add html-webpack-plugin -D
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
// ...
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html", // 这里设置自己模板文件
}),
],
};
CleanWebpackPlugin
第二个问题的解决办法是在打包之前清除输出目录中的内容,然后让它重新生成。CleanWebpackPlugin 插件虽然不是官方的,但是在 5.20.0 之前的版本中仍然值得推荐。
yarn add clean-webpack-plugin -D
output.clean
在 webpack 5.20.0+ 的版本中,内置了清除输出目录内容的功能,只要在 output 选项中配置一个参数即可。
// webpack.config.js
module.exports = {
//...
output: {
clean: true, // Clean the output directory before emit.
},
};
1. 开发环境中的 source map
webpack.config.js
module.exports = {
mode: 'development',
devtool: 'eval-cheap-module-source-map' // development
}
复制代码
2. 生产环境中的 source map
module.exports = {
mode: 'production',
devtool: 'nosources-source-map', // production
}
DevServer
watch
监听打包
// package.json
{
"scripts": {
"watch": "webpack --watch", // 监听打包
"bundle": "webpack" // 普通打包
},
}
webpack-dev-server (本地开发服务器)
yarn add webpack-dev-server -D
package.json
{
"scripts": {
"start": "webpack serve", // 开启本地服务器
"watch": "webpack --watch", // 监听打包
"bundle": "webpack" // 普通打包
},
}
复制代码
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// ...
mode: 'development',
devtool: 'eval-cheap-module-source-map',
devServer: {
static: {
directory: path.join(__dirname, 'dist'),// 指定被访问html页面所在目录的路径
},
client: {
progress: true, // 在浏览器中以百分比显示编译进度。
},
open: true, // 开启服务器时,自动打开页面
compress: true, // 开启 gzip 压缩
port: 8888, // 自定义端口号
hot: true, // 开启热更新
hotOnly: true, // 强制热更新,不会刷新页面
},
// ...
}
复制代码
注意:
- 在开发环境中,mode、devtool、devServer这三个配置是非常重要的!
- webpack-dev-server 在编译后不会在输出目录写入任何文件。相反,它会将打包的文件存在内存中,就好像它们被安装在服务器根路径上的真实文件一样。如果希望在其他路径上找到打包的文件,可以通过使用 devServer 中的 publicPath 选项更改此设置。
- 为了以模板为支撑更有效率地输出打包文件,我们需要 HtmlWebpackPlugin;
- 为了快速定位代码错误的位置,我们需要 source map;
- 为了更好地模拟真实环境进行开发,我们需要 devServer(WDS);
- 为了实时局部更新修改的内容而非全局更新,我们需要 Hot Module Replacement(HMR)!
Babel配置
有一些版本的浏览器对于JS新的语法(例如 ES6+)的支持不好,这时就需要将新的语法转换成 ES5 标准的语法,让浏览器正常识别它们,保证程序的稳定运行。
为了实现这种转换,我们该怎么办呢?用 Babel,它会把新语法转换为旧语法。
yarn add babel-loader @babel/core @babel/preset-env -D
// webpack.config.js
module: {
rules: [
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
}
]
}
// babel.config.json
{
"presets": [
["@babel/preset-env", {
"useBuiltIns": "usage", // 按需引入 corejs 中的模块
"corejs": 3, // 核心 js 版本
"targets": "> 0.25%, not dead" // 浏览器支持范围
}]
]
}
还需要下载的依赖:
npm i core-js@3 --save
yarn add @babel/plugin-transform-runtime -D
yarn add @babel/runtime
yarn add @babel/runtime-corejs3
Tree Shaking
optimization: {
usedExports: true,
}
webpack-merge
yarn add webpack-merge -D
配置typescript
yarn add core-js
yarn add -D babel-loader @babel/core @babel/cli @babel/preset-env
yarn add @babel/polyfill
yarn add -D @babel/preset-typescript
yarn add typescript
yarn add -D ts-loader
// ts配置
{
test: /\.(jsx|ts|tsx)$/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
{
loader: 'ts-loader',
options: {
compilerOptions: {
noEmit: false,
},
},
},
],
exclude: /node_modules/
},
{
"compilerOptions": {
"target": "es6",
"experimentalDecorators": false,
"module": "esnext",
"lib": [
"dom",
"dom.iterable",
"esnext",
"webworker"
],
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": false,
"noEmit": true,
"jsx": "react",
"noFallthroughCasesInSwitch": true,
"typeRoots": [
"./src/types",
"./node_modules/@types"
],
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": [
"src"
],
"exclude": [
"node_modules",
"./mock"
]
}
配置react
yarn add react react-dom @types/react @types/react-dom
项目babel
配置
yarn add -D @babel/core @babel/preset-env babel-loader @babel/plugin-transform-runtime @babel/preset-react
// babelrc,json
{
"presets": [
"react-app",
[
"@babel/preset-env",
{
"useBuiltIns": "entry",
"corejs": 3
}
]
],
"plugins": [
"@babel/plugin-proposal-class-properties",
["import", { "libraryName": "antd-mobile", "libraryDirectory": "lib", "style": "css" }, "antd-mobile"],
["import", { "libraryName": "antd", "libraryDirectory": "lib", "style": true }, "antd"]
]
}
Eslint
& prettier
完成了核心的应用流程打包代码,接下来我们来聊一些轻松的代码检查。
一份良好的工程架构代码规范检查是必不可少的配置。
prettier
yarn add --dev --exact prettier
复制代码
安装完成之后我们在项目根目录下:
echo {}> .prettierrc.js
复制代码
我们来个这个js
内容添加一些基础配置
module.exports = {
printWidth: 100, // 代码宽度建议不超过100字符
tabWidth: 2, // tab缩进2个空格
semi: false, // 末尾分号
singleQuote: true, // 单引号
jsxSingleQuote: true, // jsx中使用单引号
trailingComma: 'es5', // 尾随逗号
arrowParens: 'avoid', // 箭头函数仅在必要时使用()
htmlWhitespaceSensitivity: 'css', // html空格敏感度
}
复制代码
我们再来添加一份.prettierignore
让prettier
忽略检查一些文件:
//.prettierignore
**/*.min.js
**/*.min.css
.idea/
node_modules/
dist/
build/
复制代码
同时让我们为我们的代码基于husky
和lint-staged
添加git hook
具体配置可以参照这里[husky&list-staged](https://link.juejin.cn?target=https%3A%2F%2Fprettier.io%2Fdocs%2Fen%2Fprecommit.html%23option-1-lint-stagedhttpsgithubcomokonetlint-staged)
安装完成后,在我们每次commit
时候都会触发lit-staged
自动修复我们匹配的文件:
因为我们项目中是ts
文件,所以要稍微修改一下他支持的后缀文件:
// package.json
...
"lint-staged": {
"*.{js,css,md,ts,tsx,jsx}": "prettier --write"
}
...
复制代码
ESlint
Eslint
其实就不用多说了,大名鼎鼎嘛。
yarn add eslint --dev
复制代码
初始化eslint
npx eslint --init
复制代码
eslint
回和我们进行一些列的交互提示,按照提示进行选择我们需要的配置就可以了:
当
prettier
和eslint
共同工作时,他们可能会冲突。我们需要安装yarn add -D eslint-config-prettie
插件并且覆盖eslint
部分规则。
安装完成之后,我们稍微修改一下eslint
的配置文件,让冲突时,优先使用prettier
覆盖eslint
规则:
// .eslint.js
module.exports = {
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended",
// 添加`prettier`拓展 用于和`prettier`冲突时覆盖`eslint`规则
"prettier"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": [
"react",
"@typescript-eslint"
],
"rules": {
}
};
复制代码
同时我们来添加.eslintignore
忽略掉一些我们的非ts
目录文件,比如构建的一些脚本文件。
*.d.ts
scripts/**
优化打包体积
使用CompressionPlugin对文件进行压缩
yarn add compression-webpack-plugin -D
const CompressionPlugin = require("compression-webpack-plugin")
plugins:[
new CompressionPlugin({
threshold: 0,
minRatio:0.6,
test:/\.(css|js)/i,
algorithm:"gzip"
})
]
优化打包速度,开启DLL
// 输出文件
output: {
publicPath: './',
path: path.resolve(__dirname, 'dist'),
filename: '[name].dll.js',
assetModuleFilename: 'assets/[name]_[hash][ext]',
clean: true,
// 存放动态链接库的全局变量名称,例如对应 react 来说就是 _dll_react
// 之所以在前面加上 _dll_ 是为了防止全局变量冲突
library: '_dll_[name]',
},
const path = require('path')
const DllPlugin = require('webpack/lib/DllPlugin')
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
}),
new CompressionPlugin({
threshold: 0,
minRatio: 0.6,
test: /\.(css|js)/i,
algorithm: 'gzip',
}),
new HTMLWebpackPlugin({
template: './src/index.html',
inject: 'body',
cache: true,
minify: {
removeComments: true, // 是否删除注释
removeRedundantAttributes: true, // 是否删除多余(默认)属性
removeEmptyAttributes: true, // 是否删除空属性
collapseWhitespace: false, // 折叠空格
removeStyleLinkTypeAttributes: true, // 比如link中的type="text/css"
minifyCSS: true, // 是否压缩style标签内的css
minifyJS: {
// 压缩JS选项,可参考Terser配置
mangle: {
toplevel: true,
},
},
},
}),
new DllPlugin({
// 动态链接库的全局变量名称,需要和 output.library 中保持一致
// 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值
// 例如 react.manifest.json 中就有 "name": "_dll_react"
name: '_dll_[name]',
// 描述动态链接库的 manifest.json 文件输出时的文件名称
path: path.join(__dirname, '[name].manifest.json'),
}),
new DllReferencePlugin({
// 描述 react 动态链接库的文件内容
manifest: require(path.join(__dirname, 'main.manifest.json')),
}),
],