步骤一:初始化webpack
npm init -y
创建目录 public,scropts,src
步骤二:创建文件 .gitignore 配置忽略提交文件
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
步骤三:
创建文件 webpack.common.js:用于编写公共配置
创建文件 webpack.dev.js:用于编写测试环境配置
创建文件 webpack.prod.js:用于编写生产打包环境配置
虽然都分开了配置,但是在公共配置中,还是可能会出现某个配置的某个选项在开发环境和生产环境中采用不同的配置,这个时候有两种选择:
- 分别在 dev 和 prod 配置文件中写一遍,common 中就不写了
- 设置某个环境变量,根据这个环境变量来判别不同环境
为了代码优雅性选择第二种方案,下载所需第三方包cross-env :统一配置Node环境变量
npm i cross-env
步骤四:配置webpack
1,安装webpack相关包
npm install --save-dev webpack
npm install --save-dev webpack-cli
npm install --save-dev webpack-dev-server
npm i webpack-merge@5.7.3
npm install --save-dev html-webpack-plugin
2,添加html模板文件和webpack入口文件index.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
// src/index.js
const ele = document.querySelector('#root')
ele.innerHTML = '测试'
3,添加启动项目配置
// package.json
"scripts": {
"start": "cross-env NODE_ENV=development webpack-dev-server --config ./scripts/config/webpack.dev.js",
"build": "cross-env NODE_ENV=production webpack --config ./scripts/config/webpack.prod.js"
},
4,配置公共配置,然后分别引入开发、生产环境配置文件。
// webpack.common.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { PROJECT_PATH } = require('../constant')
module.exports = {
entry: {
app: path.resolve(PROJECT_PATH, './src/app.js')
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(PROJECT_PATH, './index.html'),
}),
]
}
5,配置开发环境,引入公共配置并且添加 webpack-dev-server的配置
const { merge } = require('webpack-merge')
const path = require('path')
const webpack = require('webpack')
const common = require('./webpack.common')
const { PROJECT_PATH, SERVER_HOST, SERVER_PORT } = require('../constant')
module.exports = merge(common, {
mode: 'development',
devtool: 'cheap-module-source-map',
output: {
filename: 'js/[name].js',
path: path.resolve(PROJECT_PATH, './dist')
},
devServer: {
host: SERVER_HOST, // 服务ip
port: SERVER_PORT, // 服务端口
// stats: 'errors-only', // 设为errors-only表示终端只打印错误类型的日志,不会打印warning以及其他信息影响阅读
// clientLogLevel: 'none', // 设为none表示去除多余网页console信息
compress: true, // 设为true表示启用gzip压缩,加快网站打开速度
open: true, // 设为true表示第一次启动项目时自动打开默认浏览器
hot: true, // 设为true表示启用服务热替换配置
// noInfo: true, // 设为true表示去除启动项目时显示的打包信息
https: true, // 启动项目的时候,默认是https协议
},
plugins: [
// 实际上只开启 hot:true 就会自动识别有无声明该插件,没有则自动引入,但是怕有隐藏问题这里还是手动加上了
new webpack.HotModuleReplacementPlugin()
]
})
小工具
webpackbar:用于显示打包进度条,会更有体验感
npm install webpackbar -D
// webpack.common.js
const WebpackBar = require('webpackbar')
module.exports = {
plugins: [
new WebpackBar({
name: 'Link Startou!!!',
color: '#52c41a'
}),
]
}
6,配置生产环境
同样引入公共配置,生产环境一般输出文件都会加上哈希值,而开发环境不需要,开发环境使用 webpack-dev-server 启动时不会在项目中真正的产生文件,而是存在了内存中,主要是为了打包。
// webpack.prod.js
const { merge } = require('webpack-merge')
const path = require('path')
const common = require('./webpack.common')
const { PROJECT_PATH } = require('../constant')
module.exports = merge(common, {
mode: 'production',
devtool: false,
output: {
filename: 'js/[name].[contenthash:8].js',
path: path.resolve(PROJECT_PATH, './dist')
},
})
步骤五:CSS相关配置
1,配置css
npm install --save-dev style-loader
npm install --save-dev css-loader
npm install --save-dev postcss-loader postcss
npm install postcss-preset-env --save-dev
style-loader:将 js 文件中引入的 css 代码插入到 html 模板文件,使网页可以正常展示样式
css-loader:令 js 可以通过 import 或者 require 等命令导入 css 代码
postcss-loader postcss:与 sass/less 不同,不是预处理器,相当于一个工具箱,可以使用它配合插件去转换css
postcss-preset-env:将最新的 css 语法转换为目标环境的浏览器能够理解的语法,不用考虑浏览器兼容问题,以前需要配合 autoprefixer 第三方包自动补全前缀,现在新版本已经内置autoprefixer功能了
2,配置预处理器
npm install less less-loader --save-dev
npm install node-sass --save-dev
npm install sass-loader sass webpack --save-dev
less:为 less-loader 提供依赖
less-loader:将 less 代码转换为 css 代码
node-sass:为 sass-loader 提供依赖
sass-loader:将 sass 代码转换为 css 代码
配置less
// webpack.common.js module.rules
{
test: /\.less$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[name]-[local]-[hash:base64:8]',
},
},
},
{
loader: 'postcss-loader',
},
{
loader: 'less-loader',
},
],
},
配置sass
// webpack.common.js module.rules
{
test: /\.scss$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[name]-[local]-[hash:base64:8]',
},
},
},
{
loader: 'postcss-loader',
},
{
loader: 'sass-loader',
},
],
},
步骤六:Babel相关配置
兼容 ES6+
npm install -D babel-loader @babel/core @babel/preset-env
npm install @babel/runtime-corejs3
npm install --save-dev @babel/plugin-transform-runtime
babel-loader:用于处理 ES6+ 语法,将其编译为浏览器可以执行的 js
@babel/core:babel 所需依赖
@babel/preset-env:是一组ES6转译的plugins,会根据设置的目标浏览器环境(browserslist),选择该环境不支持的新特性进行转译,这样就减少了不必要转译,增快打包速度。
@babel/plugin-transform-runtime:提供 ES6+ 的 api,如 es6 新的数组方法等,和 @babel/polyfill 不同的是该包可以实现按需加载,不会全部引入影响打包速度,需要依赖 runtime-corejs。
@babel/runtime-corejs3:相当于 @babel/polyfill 的功能,在 7.4.0 版本后的 babel 使用 runtime-core 代替了 babel/polyfill。
1,Babel 在执行编译的过程中,会从项目的根目录下的 .babelrc 文件中读取配置(本质上是json格式文件),因此此时需要在项目根目录新建该文件
// .babelrc
{
"presets": [
[
"@babel/preset-env",
{
"modules": false
}
]
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": {
"version": 3,
"proposals": true
}
}
]
]
}
2,给 webpack 配上对 js 文件应用,一并配置tsx和jsx
// webpack.common.js module.rules
{
test: /\.[jt]sx?$/,
exclude: /node_modules/, // 排除 node_modules 文件夹
use: {
loader: 'babel-loader', // babel-loader babel-loader处理JSX语法的。
options: {
babelrc: true,
cacheDirectory: true,
},
},
},
步骤七:支持React
npm i react react-dom @types/react @types/react-dom @babel/preset-react
react:react核心依赖
react-dom:负责处理web端的dom的渲染
@types/react :react 类型声明文件,用于 tsx
@types/react-dom:react-dom 类型声明文件,用于 tsx
@babel/preset-react :用于让 babel 可以转译 jsx 语法
配置.babelrc
{
"presets": [
[
"@babel/preset-env",
{
"modules": false
}
],
"@babel/preset-react"
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": {
"version": 3,
"proposals": true
}
}
]
]
}
步骤八:支持ts
npm install --save typescript @babel/preset-typescript
typescript:支持 ts
@babel/preset-typescript:处理ts文件,原理是去掉ts的类型声明,然后再用其他 babel 插件进行编译
1,修改 .babelrc
"presets": [
[
"@babel/preset-env",
{
"modules": false
}
],
"@babel/preset-react",
"@babel/preset-typescript"
],
2,配置 tsconfig.json
{
"compilerOptions": {
"target": "ES5", /* 指定 ECMAScript 目标版本:'ES3'、'ES5'(默认)、'ES2015'、'ES2016'、'ES2017'、'ES2018'、'ES2019'、'ES2020' 或 'ESNEXT'。 */
"lib": [
"dom",
"dom.iterable",
"esnext"
], /* 指定要包含在编译中的库文件。 */
// "allowJs": true, /* 允许编译 javascript 文件。打开会导致check到nvm中的一些js文件 */
"skipLibCheck": true, /* 跳过声明文件的类型检查。 */
"esModuleInterop": true, /* 通过为所有导入创建命名空间对象,在 CommonJS 和 ES 模块之间启用发射互操作性。 暗示“allowSyntheticDefaultImports”。 */
"allowSyntheticDefaultImports": true, /* 允许从没有默认导出的模块中默认导入。 这不会影响代码发出,只是类型检查。 */
"strict": true, /* 启用所有严格的类型检查选项。 在开发中,建议将stricet这类选项都开启。 */
"forceConsistentCasingInFileNames": true, /* 禁止对同一文件的大小写不一致的引用。 */
"noFallthroughCasesInSwitch": true, /* 在 switch 语句中报告失败情况的错误。 */
"module": "esnext", /* 指定模块代码生成:“none”、“commonjs”、“amd”、“system”、“umd”、“es2015”、“es2020”或“ESNext”。 */
"moduleResolution": "node", /* 指定模块解析策略:'node' (Node.js) 或 'classic' (TypeScript pre-1.6)。 */
"resolveJsonModule": true, //允许导入扩展名为“.json”的模块
"isolatedModules": true, //将每个文件作为单独的模块(与“ts.transpileModule”类似)。
// "noEmit": true, /* 不发出输出(不生成编译后的文件)。 */
"jsx": "react", /* 指定 JSX 代码生成:'preserve'、'react-native' 或 'react'。 */
},
"include": [
"src/**/*"
], //包含的文件
}
由于使用了 ts,在导入文件的时候可能会出现类型错误,如 css module 的 import style from ‘./index.scss’ 报错 找不到模块“./index.scss”或其相应的类型声明,因此需要手动编写声明类型文件,在 src 目录下新建 file.d.ts 文件
// src/file.d.ts
declare module "*.css" {
const style: any;
export default style
}
declare module "*.scss" {
const style: any;
export default style
}
declare module "*.less" {
const style: any;
export default style
}
步骤九:优化
1,webpack 可以省略后缀名查找并处理文件
module.exports = {
resolve: {
extensions: ['.js', '.jsx', '.tsx', '.ts', '.json'],
},
}
2,terser-webpack-plugin:用去去除生产环境的无用js代码,webpack5 之后自带,不需要另行安装,直接引入使用
// webpack.prod.js
const TerserPlugin = require("terser-webpack-plugin")
module.exports = merge(common, {
optimization: {
minimize: true,
minimizer:[
new TerserPlugin({
extractComments: false,
terserOptions: {
compress: { pure_funcs: ['console.log'] },
}
}),
]
}
})
extractComments:设为 false 表示去除所有注释,除了有特殊标志的注释如 @preserve 标记
pure_funcs:去除函数,如上述配置的意思是将所有 console.log 函数去除
3,fork-ts-checker-webpack-plugin:ts 类型检查,让终端可以显示类型错误
npm install --save-dev fork-ts-checker-webpack-plugin
// webpack.common.js
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')
module.exports = {
plugins: [
new ForkTsCheckerWebpackPlugin({
typescript: {
configFile: path.resolve(PROJECT_PATH, './tsconfig.json'),
},
}),
],
}
步骤十:配置资源
之前处理图片字体之类的资源的时候等,需要用到url-loader,file-loader
现在webpack5 已内置资源模块,因此无需再下载直接配置即可,更多详情参考 【webpack5 新特性asset】
asset/resource:file-loader的功能。
asset/inline:url-loader的功能。
asset:使用这个配置,默认小于 8kb 的文件将被视为inline模块类型,将转为 Data URI 以内联的方式使用;大于 8kb 的文件将被视为resource 模块类型,打包成单独的文件,之前有url-loader设置asset size limit 限制实现
{
test: /\.(png|jpg|jpeg|mp4|gif|mov)$/i,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 10 * 1024, // 小于10kb的会被base64处理
},
},
},
{
test: /\.(dae|eot|woff|woff2|svg|ttf|otf)$/,
type: 'asset/resource'
},
规定打包目录,配置一下生产环境的打包出口
// scripts/config/webpack.prod.js
module.exports = merge(common, {
output: {
// ...other
assetModuleFilename: 'images/[name].[contenthash:8].[ext]',
},
})
因为使用的是 ts,导入上述文件后缀名会产生不存在的错误,因此继续添加声明文件
// src/file.d.ts
declare module '*.png' {
const path: string
export default path
}
declare module '*.jpg' {
const path: string
export default path
}
declare module '*.jpeg' {
const path: string
export default path
}
declare module '*.gif' {
const path: string
export default path
}
declare module '*.mov' {
const path: string
export default path
}
declare module '*.svg' {
const path: string
export default path
}
//.......
处理非动态导入静态资源
copy-webpack-plugin:处理不需要动态导入的静态资源文件,将其复制进打包目录
npm install copy-webpack-plugin --save-dev
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
plugins: [
new CopyWebpackPlugin({
patterns: [
{
from: path.join(__dirname, '../../src/resource'),
to: path.join(__dirname, '../../dist/resource'),
},
],
}),
]
}
测试
将src下的index.js修改为index.tsx并且修改webpack.common.js入口文件
注意:react18之后废弃了ReactDOM.render,改用createRoot
import React from 'react'
import ReactDOM from 'react-dom/client'
import styles from './app.scss'
const container = document.getElementById('root') as HTMLElement
const root = ReactDOM.createRoot(container)
root.render(<div className={styles.title}>阶段测试</div>)