代码仓库:
安装包
- webpack
-
webpack: 构建工具,将多个模块打包成一个或多个静态文件 -
webpack-cli: webpack命令行工具 -
webpack-dev-server: 开发服务器,提供实时加载和热更新 -
webpack-merge: 合并webpack配置文件
- babel
-
babel-loader: webpack的loader,使webpack可以转换代码 -
@babel/core: Babel 的核心模块,提供了转换代码的 API -
@babel/perset-env: 是babel的预设,可以将最新版的JavaScript转为兼容的版本
- CSS
-
style-loader: 将css注入到HTML页面的<style></style>中 -
css-loader: 解析CSS文件,及文件中@import和url(),并生成依赖关系 -
postcss: PostCSS是一个处理CSS的工具框架,本身没有功能 -
postcss-loader: 在webpack构建过程中使用PostCSS构建CSS文件 -
post-preset-env: postCSS的插件,自动为CSS添加浏览前缀;把现代css转成浏览器认识的css -
mini-css-extract-plugin: 将css提交到单独的文件中 (与style-loader互斥) -
sass: CSS预处理器 -
sass-loader: 处理.scss文件 -
@types/node-sass: sass的ts支持
- vue
-
vue: -
vue-loader: 把.vue文件的各个模块提取出来,然后经过对用的loader处理 -
@vue/compiler-sfc: 将template语法转换成render函数 -
eslint-plugin-vue: vue代码检查插件 -
@type/vue
- react
-
React -
React-DOM -
@babel/preset-react -
eslint-plugin-react -
@types/react: -
@types/react-dom: -
classnames:
- 代码规范
-
eslint: 代码检查工具 -
prettier: 代码格式化工具 -
eslint-config-prettier: 用于关闭ESLint和Pettier冲突的规则 -
eslint-plugin-prettier: 用于将Pettier的规则加入eslint中 -
husky: 基于Git Hooks的工具,可以在Git Hooks事件执行脚本 -
lint-staged: 在git中暂存的文件上运行任务 -
commitlint: commit提交信息规范
- TypeScript:
-
typescript -
@typescript-eslint/parse: ESLint的解析器,把ts转成eslint可以理解的AST -
@typescript-eslint/eslint-plugin: eslint的插件,检查的ts代码 -
@babel/preset-typescript: 使用babel解析ts, TypeScript的预设 -
esbuild-loader:
- 其他:
- ~~~~ webpack5已集成clean-webpack-plugin~~~~: 删除构建文件
-
dotenv-webpack: 可以在webpack构建时加载环境变量;将.env环境变量加载到process.env对象中 -
cross-env: 跨平台的环境变量设置工具
搭建项目
基础搭建
- 安装webpack
npm init -y
pnpm i webpack webpack-cli webpack-dev-server -D
- 处理html文件
pnpm i html-webpack-plugin -D
- 构建前删除上一次构建文件
pnpm i ~~~~clean-webpack-plugin~~~~ -D
output.clean 设置为true
- 配置
// webpack.config.js
const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash:8].js',
clean: true
},
plugins: [
new HTMLWebpackPlugin({
template: './public/index.html',
})
],
devServer: {
open: true,
hot:true
}
}
// package.json
{
"scripts": {
"serve": "webpack serve --config webpack.config.js"
}
}
处理 JavaScript 文件
- 安装依赖
pnpm i @babel/core @babel/preset-env babel-loader -D
- 配置项
// webpack.config.js
module: {
rules: [
{
test: /.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
}
]
}
// .babelrc
{
"presets": [
"@babel/preset-env"
]
}
处理 CSS 文件
解析 CSS 文件
- 安装依赖
pnpm i style-loader css-loader sass sass-loader mini-css-extract-plugin -D
- 配置rules
mini-css-extract-plugin和style-loader不能同时使用,需要根据不同的环境变量使用不同的loader
// webpack.config.js
const isDevelopment = process.env.NODE_ENV !== 'production'
const style = isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader
{
module: {
rules: [
{
test: /.css$/,
use: [
style,
'css-loader'
]
},
{
test: /.scss$/,
use: [
style,
'css-loader',
'sass-loader'
]
}
]
}
}
使用PostCSS处理CSS文件
- 安装依赖
pnpm i postcss postcss-loader postcss-preset-env -D
- 配置
rules
{
test: /.css$/,
use: [
style,
'css-loader',
'postcss-loader'
]
},
{
test: /.scss$/,
use: [
style,
'css-loader',
'postcss-loader' // sass-loader转换成css后交给postcss处理
'sass-loader',
]
}
- 在根目录下新建
postcss.config.js
module.exports = {
plugins: [
require('postcss-preset-env')({
autoprefixer: {
grid: true,
},
})
]
}
- 配置
browserslist, 指定支持的浏览器. browserslist(webpack5已经集成)
// .browserslistrc
last 2 versions
>1%
解析图片等文件
webpack5之前file-loader、url-loader、raw-loader 对文件处理,webpack5已经集成了这些,只需要配置即可
// webpack.config.js
moduel.exports = {
module: {
rules: [
{
test: /.(png|jpe?g)$/,
type: 'asset',
parse: {
dataUrlCondition: {
maxSize: 1024 * 1000 // 1M
}
}
},
{
test: /.(svg$)/i,
type: "asset/source"
}
]
}
}
区分不同的环境
- 安装依赖
pnpm i webpack-merge cross-env ``dotenv-webpack`` -D
- 拆分
webapack.config.js
根据需要把webapack.config.js拆成多个文件:
-
webapack.common.js -
webapack.dev.js: 开发环境时用到的配置 -
webpack.prod.js: 生成环境时用到的配置
-
配置
- 修改
package.json命令
- 修改
cross-env适配不同平台设置环境变量;设置环境变量名,并读取不同的配置文件
// package.json
{
"scripts":{
"serve": "cross-env NODE_ENV=development webpack server --config ./build/webpack.dev.js",
"build": "cross-env NODE_ENV=production webpack --config ./build/webpack.prod.js"
}
}
- 新建
build/webpack.common.js文件
主要改动:
-
移出devServer到
webpack.dev.js -
移除output到
webpack.prod.js -
根据环境变量配置
style-loader和MiniCssExtractPlugin.loader
const HTMLWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const isDevelopment = process.env.NODE_ENV!== 'production';
const style = isDevelopment? 'style-loader' : MiniCssExtractPlugin.loader;
module.exports = {
entry: './src/index.js',
module: {
rules: [
{
test: /.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
},
{
test: /.css$/,
use: [
style,
'css-loader'
]
},
{
test: /.scss$/,
use: [
style,
'css-loader',
'sass-loader'
]
}
]
},
plugins: [
new HTMLWebpackPlugin({
template: './public/index.html',
}),
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css'
})
],
}
- 新建
build/webpack.dev.js文件
主要功能: 使用webpack-merge的merge方法合并webpack配置
const {merge} = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'development',
devServer: {
open: true,
hot:true
}
})
- 新建
build/webpack.prod.js文件
const path = require('path');
const {merge} = require('webpack-merge');
const common = require('./webpack.common.js')
module.exports = merge(common, {
mode: 'production',
output: {
path: path.resolve(__dirname, '../dist'),
filename: 'js/[name].[contenthash:8].js'
},
})
- 在业务中使用环境变量
dotenv-webpack是dotenv和DefinePlugin的包装
可以读取.env下的配置文件注入到环境变量中
// webpack.common.js
const DotenvWebpack = require('dotenv-webpack')
{
plugins: [
new DotenvWebpack({
path: '.env.' + process.env.NODE_ENV,
systemvars: true,
}),
]
}
使用TypeScript
- 使用
esbuild-loader解析.ts文件 - 安装依赖
pnpm i esbuild esbuild-loader @esbuild/darwin-arm64 -D
电脑是ARM芯片的需要安装@esbuild/darwin-arm64
- 配置
- 配置rules
// webpack.common.js
{
module: {
rules: [
{
test: /.ts$/,
exclude: /node_modules/,
use: {
loader: 'esbuild-loader',
options: {
loader: 'ts',
target: 'es2015'
}
}
},
]
}
}
- 根目录下新建
ts.config.json
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"lib": ["es6", "dom"],
"jsx": "react",
"strict": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true
}
}
搭建Vue3项目
Vue项目需要处理.vue文件
- 安装依赖
pnpm i vue-loader @vue/compiler-sfc -D
pnpm i vue
搭建React项目
- 安装依赖
pnpm i react react-dom
pnpm i @type/react @type/react-dom -D
- 配置
- 配置rules
{
module: {
rules: {
test: /.[jt]sx?$/,
loader: 'esbuild-loader',
options: {
loader: 'tsx',
target: 'es2015'
}
}
}
}
这样就可以开始React了
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App'
createRoot(document.getElementById('app') as HTMLElement).render(
<App message="Hello, world!"/>
)
模版文件复制
利用plop快速新建模版页面代码
- 安装依赖
pnpm i -D plop node-plop
- 在根目录下新建
plopfile.js
module.exports = plop => {
plop.setGenerator('new', {
description: '创建一个新页面', // 描述
// 命令式交互配置
prompts: [
{
type: 'input',
name: 'name',
message: '页面名称',
default: 'default-page',
},
],
actions: [
{
type: 'add',
path: 'src/page/{{name}}/index.tsx',
templateFile: './template/index.tsx',
},
{
type: 'add',
path: 'src/page/{{name}}/index.scss',
templateFile: './template/index.scss',
},
],
})
}
- 在根目录下新建
template目录,并在该目录下新建模版文件 - 在
package.json添加命令
"scripts": {
"new": "plop new"
}
运行pnpm new,提示输入页面名称,就会创建新的模版文件
团队协作及优化
代码规范
主要规范:
- 代码风格规范
- git提交规范
代码风格规范
- 安装依赖
pnpm i -D eslint prettier eslint-config-prettier eslint-plugin-prettier
- TS文件代码检查
pnpm i @typescript-eslint/parser @typescript-eslint/eslint-plugin -D
- Vue文件代码检查
pnpm i eslint-plugin-vue -D
- React文件代码检查
pnpm i eslint-plugin-react eslint-plugin-react-hooks -D
- 根目录下新增配置文件
.eslintrc.js
// .eslintrc.js
module.exports = {
env: {
browser: true,
es6: true,
node: true,
},
parser: "@typescript-eslint/parser", // Specifies the ESLint parser
parserOptions: {
ecmaVersion: 2020,
sourceType: "module",
ecmaFeatures: {
jsx: true,
},
},
plugins: ["react", "react-hooks", "@typescript-eslint", "prettier"],
extends: [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
],
rules: {
"@typescript-eslint/no-var-requires": "off",
},
};
- 自动格式化
- 安装依赖
pnpm i husky lint-staged -D
- 执行配置
新建husky配置; 在pre-commit 钩子执行lint-staged
npx husky add .husky/pre-commit "pnpm lint-staged"
- 配置
package.json
//package.json
{
"lint-staged": {
"**/*.{js,vue,tsx,ts,less,md,json}": [
"eslint --fix",
"prettier --config .prettierrc --write",
"git add"
]
}
}
Git提交信息规范
- 安装依赖
pnpm i commitlint @commitlint/{cli,config-conventional} -D
- 配置
- 配置
.commitlintrc.js
module.exports = {
extends: ['@commitlint/config-conventional'],
}
- 新建
husky配置; 在commit-msg钩子执行命令
npx husky add .husky/commit-msg " npx --no-install commitlint --edit $1"
这样在commit 的时候,就会校验commit msg是否规范
Git 提交信息自动规则命令行工具
- 安装依赖
pnpm i commitizen cz-conventional-changelog-zh -D
- 配置
配置package.json
{
"scripts": {
commit: "git add .& git-cz"
},
"config": {
"commitizen": {
"path": "cz-conventional-changelog-zh"
}
}
}
添加.czrc文件
{
"extends": [
"cz-conventional-changelog-zh"
],
"path": "cz-conventional-changelog-zh"
}
运行pnpm commit 就可以根据提示输入提交信息了
单元测试
使用jest测试框架进行单元测试
React
- 安装依赖
pnpm i -D jest @types/jest esbuild-jest ts-jest esbuild
pnpm i -D @testing-library/react @testing-library/jest-dom @types/testing-library__jest-dom
jest: JavaScript 测试框架,进行写测试用例和断言esbuild-jest: Jest转换器,解析Jest中的JavaScript和TypeScript代码@testing-library/react: 是一个 React 测试工具库,提供了一些用于编写 UI 组件测试的实用工具函数。如:render渲染组件@testing-library/jest-dom: 是一个 Jest 匹配器库,用于更方便地进行 DOM 元素测试,提供了一些用于测试 DOM 元素的实用工具函数。
- 配置
- 配置
jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
transform: {
'^.+\.(ts|tsx)$': 'esbuild-jest', // 指定了将 ts、tsx 文件使用 esbuild-jest 进行转换
},
testRegex: '(/__tests__/.*|(\.|/)(test|spec))\.tsx?$',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect'],. // 指定了 jest-dom 的扩展包。
}
- 配置
ts.config.js
{
"compilerOptions": {
"target": "es2017",
"module": "esnext",
"moduleResolution": "node",
"lib": ["es6", "dom"],
"jsx": "react",
"strict": true,
"baseUrl": ".",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true,
"paths": {
"@/*": ["src/*"]
}
}
}
指定 tsconfig.json 中的 module 为 esnext,同时开启 esModuleInterop。
- 编写测试用例
// Button.test.tsx
import React from 'react';
import { render, screen } from '@testing-library/react';
import Button from './Button';
test('renders a button', () => {
render(<Button text="Click me" onClick={() => {}} />);
const buttonElement = screen.getByText(/click me/i);
expect(buttonElement).toBeInTheDocument();
});
运行pnpm test 就可以进行测试了。
构建优化
在webpack5中可以通过一些方法,对构建过程进行优化:
-
持久化缓存
引入缓存后,首次构建时间将增加 15%,二次构建时间将减少 90%
// webpack.common.js module.exports = { cache: { type: 'filesystem', // 使用文件缓存 } }
-
多进程打包
Webpack5提供了内置的多进程并行打包功能,可以通过配置parallelism选项来开启多进程打包
const { ProgressPlugin } = require('webpack')
{
plugins: [
new ProgressPlugin()
],
optimization: {
parallelism: 4, // 开启4个worker
}
}
-
在开发阶段关闭不必要的优化
module.exports = {
// ...
mode: "development",
optimization: {
removeAvailableModules: false,
removeEmptyChunks: false,
splitChunks: false, // 关闭代码分包
minimize: false, // 关闭代码压缩
concatenateModules: false, // 关闭模块合并
usedExports: false, // 关闭 Tree-shaking 功能
},
};
-
慎用
source-map
开发环境使用eval, 获取最佳的编译速度
生产环境使用source-map, 获取最高的质量
-
优化
resolve配置
resolve是配置webpack如何解析模块,可通过优化resolve配置,减少解析范围
- alias
alias 可以创建 import 或 require 的别名,用来简化模块引入。
module.exports = {
resolve: {
alias: {
'@': paths.appSrc, // @ 代表 src 路径
},
}
}
- extensions
extensions表示解析文件的类型列表,定义类型减少文件匹配次数
module.exports = {
resolve: {
extensions: ['.tsx', '.js'], // 从右向左解析
}
}
- 关闭引入模块时,未在项目中找到后,逐层查找的功能
module.exports = {
resolve: {
modules: [path.resolve(__dirname, 'node_modules')],
},
};
-
约束loader的范围
module.exports = {
module: {
rules: [
{
test: /.css$/,
exclude: /node_modules/,
use: [style, 'css-loader', 'postcss-loader'],
},
],
}
}
产物优化
代码压缩
- webpack5内置了Terser压缩JavaScript
配置webpack.prod.js
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
reduce_vars: true,
pure_funcs: ['console.log'],
},
},
}),
],
},
- 压缩CSS
安装依赖
pnpm i -D css-minimizer-webpack-plugin
配置webpack.prod.js
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
optimization: {
minimizer: [
new CssMinimizerPlugin(),
],
}
}
抽离重复代码
webpack提供SplitChunksPlugin 插件,专门根据产物包的体积、引用次数等做分包优化。
optimization: {
splitChunks: {
chunks: 'all', // 对 Initial Chunk 与 Async Chunk 都生效
cacheGroups: {
vendors:{ // node_modules里的代码
test: /[\/]node_modules[\/]/,
chunks: "all",
// name: 'vendors', 一定不要定义固定的
namepriority: 10, // 优先级
enforce: true
}
}
},
},
- Initial Chunk:入口文件及引入的所有模块
- Async Chunk: 按需加载的模块
- 把
node_modules下的模块统一打包到vendors产物,从而实现第三方库与业务代码的分离
抽离runtimechunk(运行时)
{
optimization: {
runtimeChunk: true
}
}
抽离babel运行时
@babel/plugin-transform-runtime : 转换代码时,会把ES6+的特殊代码抽离成一个运行时,以便代码复用
- 安装依赖
pnpm i -D @babel/plugin-transform-runtime
- 配置
.babelrc
{
"plugins": [
"@babel/plugin-transform-runtime"
]
}
动态加载
HTTP缓存: 利用文件contenthash
使用外置依赖: script引入
module.exports = {
// ...
externals: {
jquery: 'jQuery'
}
};
使用Tree-shaking
Tree-Shaking只针对ESM有效
webpack中Tree-shaking的实现
-
需要先【标记】出模块中哪些值没有被使用过
-
再使用代码压缩(如:Terser)删除标记的代码
optimization: {
usedExports: true, // 启用标记
},
代码仓库: