项目初始化
创建项目
yarn init --yes
yarn add -D webpack webpack-cli webpack-dev-server cross-env react react-dom @types/react @types/react-dom typescript @babel/core @babel/cli @babel/preset-env @babel/preset-react @babel/preset-typescript ts-loader babel-loader source-map-loader less style-loader css-loader less-loader mini-css-extract-plugin postcss postcss-loader postcss-preset-env html-webpack-plugin clean-webpack-plugin
包名 | 作用 |
---|---|
webpack | Webpack 是一个模块打包工具,用于构建现代 Web 应用程序。 |
webpack-cli | Webpack 命令行工具,用于在命令行中运行和管理 Webpack。 |
webpack-dev-server | Webpack 开发服务器,用于在开发过程中提供实时刷新和热模块替换的支持。 |
react | React 是一个用于构建用户界面的 JavaScript 库。 |
react-dom | React DOM 是 React 的官方库,用于处理 Web 上的 DOM 操作。 |
@types/react | TypeScript 类型定义,支持在 TypeScript 项目中使用 React。 |
@types/react-dom | TypeScript 类型定义,支持在 TypeScript 项目中使用 React DOM。 |
typescript | TypeScript 是一个 JavaScript 的超集,具有静态类型支持。 |
@babel/core | Babel 是一个 JavaScript 编译器,用于将新版 JavaScript 代码转换为旧版浏览器支持的代码。 |
@babel/cli | Babel 的命令行工具,用于在命令行中运行 Babel 编译任务。 |
@babel/preset-env | Babel 预设,用于根据目标浏览器环境,将 ES6+ 语法转换为兼容的 JavaScript 代码。 |
@babel/preset-react | Babel 预设,支持将 React 的 JSX 语法转换为 JavaScript 代码。 |
@babel/preset-typescript | Babel 预设,支持将 TypeScript 语法转换为 JavaScript 代码。 |
ts-loader | Webpack 加载器,用于编译 TypeScript 代码。 |
babel-loader | Webpack 加载器,用于编译 Babel 预设中的代码。 |
source-map-loader | Webpack 加载器,用于处理源映射,以便在调试中使用。 |
less | LESS 是一种动态样式语言,用于构建样式表。 |
style-loader | Webpack 加载器,用于将 CSS 样式动态添加到 DOM 中。 |
css-loader | Webpack 加载器,用于处理 CSS 文件并将其添加到样式表中。 |
less-loader | Webpack 加载器,用于编译 LESS 样式文件。 |
mini-css-extract-plugin | Webpack 插件,用于提取 CSS 样式为单独的文件。 |
postcss | PostCSS 是一个用于处理 CSS 样式的工具,支持插件扩展。 |
postcss-loader | Webpack 加载器,用于在构建时运行 PostCSS 处理。 |
postcss-preset-env | PostCSS 预设,包含一组通用的 PostCSS 插件和配置。 |
html-webpack-plugin | Webpack 插件,用于生成 HTML 文件,并自动注入构建后的 JavaScript 文件。 |
clean-webpack-plugin | Webpack 插件,用于在构建前清理输出目录,确保构建输出的文件是干净的。 |
基础webpack配置
const path = require("path");
const production = process.env.NODE_ENV === 'production';
module.exports = {
// 内置优化模式,默认为production
mode: production ? 'production' : 'development',
// 控制是否生成以及如何生成source map
devtool: production ? false : 'eval-source-map',
// 路径解析别名
resolve: {
extensions: ['.ts', '.tsx', '.js', 'jsx', '.json'],
alias: {
'@': path.resolve(__dirname, 'src'),
},
}
}
添加React库
yarn add react react-dom
yarn add -D @types/react @types/react-dom
tsconfig配置
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "ES6", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
"jsx": "react", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "ES6", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
"baseUrl": ".", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
"allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}
添加Babel支持
安装Babel依赖
# Babel核心库
yarn add -D @babel/core @babel/cli
# es6语言特性转换支持
yarn add -D @babel/preset-env
# react语法转换,例如jsx支持
yarn add -D @babel/preset-react
# ts转换
yarn add -D @babel/preset-typescript
Babel配置文件.babelrc
{
"presets": [
"@babel/preset-env",
"@babel/preset-react",
"@babel/preset-typescript"
],
"plugins": ["./babel/plugins/auto-css-module-plugin"]
}
添加自定义Babel插件,实现自动启用css module
// babel/plugins/auto-css-module-plugin.js
const { extname } = require('path');
const t = require('babel-types');
const CSS_FILE_EXTENSIONS = ['.css', '.less'];
module.exports = () => {
return {
visitor: {
ImportDeclaration: (path, state) => {
const { specifiers, source } = path.node;
if (
t.isStringLiteral(source) && CSS_FILE_EXTENSIONS.includes(extname(source.value)) &&
specifiers.length > 0
) {
source.value = `${source.value}?css_modules`;
// 检查 state.file.metadata 是否存在,不存在则初始化为空对象
if (!state.file.metadata) {
state.file.metadata = {};
}
// 检查 state.file.metadata.fileModules 是否存在,不存在则初始化为空数组
if (!state.file.metadata.fileModules) {
state.file.metadata.fileModules = [];
}
// 将需要启用 CSS Modules 的文件路径保存到文件属性中
const filePath = state.file.opts.filename;
state.file.metadata.fileModules.push(filePath);
}
},
},
};
};
添加loader
webpack配置
module.exports = {
module: {
rules: [
{
test: /\.ts(x?)$/,
exclude: /node_modules/,
use: ['babel-loader', 'ts-loader'],
},
{
enforce: 'pre',
test: /\.js$/,
exclude: /node_modules/,
loader: 'source-map-loader',
}
]
}
}
less样式解析
添加postcss
// postcss.config.js
module.exports = {
plugins: ['postcss-preset-env']
};
webpack配置
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
module: {
rules: [
{
test: /\.((c|le)ss)$/,
oneOf: [
{
// 启用css modules时使用的loader
resourceQuery: /css_modules/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
{
loader: "css-loader",
options: {
esModule: true,
modules: {
localIdentName: "[name]-[local]"
},
url: {
// 过滤图片路径
filter: (url) => {
return !/^\/static\/\/images\//.test(url)
}
}
}
},
'postcss-loader',
'less-loader',
]
},
{
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'less-loader'
]
}
],
}
]
},
// 插件
plugins: [
new MiniCssExtractPlugin({
// 输出的每个css文件的名称
filename: 'static/css/[name].css',
// 按需加载的chunk文件名称
chunkFilename: 'static/css/[id].chunk.css',
}),
]
}
本插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载。
设置html模板
创建模板public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<!-- <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />-->
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<!-- <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />-->
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<!-- <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />-->
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<style>
html {
font-size: 625%;
}
body {
font-size: 16px;
}
</style>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
webpack配置
const path = require("path");
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
title: '博客模板',
template: path.resolve(__dirname, 'public', 'index.html'),
}),
]
}
创建入口文件
tsconfig配置添加jsx
语法支持
{
"compilerOptions": {
"jsx": "react"
}
}
Demo.tsx
import React from 'react'
const Demo: React.FC = () => (
<div>Hello,Word!</div>
)
export default Demo
index.tsx
import React from "react";
import { createRoot } from "react-dom/client";
import Demo from "./Demo";
const container = document.getElementById("root") as Element;
const root = createRoot(container);
root.render(<Demo />);
webpack配置
const path = require('path');
module.exports = {
entry: path.resolve(__dirname, 'src', 'index.tsx'),
output: {
filename: 'index_bundle.js',
path: path.resolve(__dirname, 'dist')
},
}
配置开发服务器
webpack配置
module.exports = {
// 开发服务器配置
devServer: {
// 静态资源配置
static: {
// 静态文件路径
directory: path.join(__dirname, 'public'),
},
// history路由必须启用,将请求重定向至index.html
historyApiFallback: true,
// 监听端口
port: 8000,
},
}
打包前清除dist文件夹
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
plugins: [
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: [path.resolve(__dirname, 'dist')]
}),
],
}
设置脚本
{
"scripts": {
"start": "webpack-dev-server --config webpack.config.js",
"build": "webpack build --config webpack.config.js"
},
}
打包优化
代码分割
module.exports = {
// Webpack的入口点,指定了你的应用程序的主要入口文件
entry: {
// 多入口配置
index: path.resolve(__dirname, 'src', 'index.tsx')
},
// Webpack的输出选项,用于指定生成的 JavaScript 文件的位置和名称。
output: {
// 定义了输出的主要 JavaScript 文件的名称,它会放在dist/static/js目录下,并根据入口点的名称来生成文件
filename: 'static/js/[name].js',
// 定义了异步加载的代码块(chunks)的文件名模板。[name]表示代码块的名称,[id]是代码块的唯一标识符。这些文件会放在dist/static/js目录下
chunkFilename: 'static/js/[name].chunk.[id].js',
path: path.resolve(__dirname, 'dist'),
publicPath: '/',
},
// 配置Webpack的代码优化策略
optimization: {
// 启用了代码分割,将代码拆分成多个文件,以便更好地利用浏览器的缓存机制
splitChunks: {
// 表示所有的代码块都会被拆分
chunks: 'all',
name: false,
// 定义了一个大小阈值,当模块的大小超过这个值时,会被拆分成一个单独的文件
maxSize: 200000,
automaticNameDelimiter: '.'
},
// 这个选项将运行时的代码拆分成一个单独的文件,这可以确保在更新应用程序时,浏览器能够保持缓存的有效性,只需要下载更新的部分。
runtimeChunk: {
name: 'runtime',
},
},
}
完整的webpack
配置
// 使用环境变量判断是否使用生产模式
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const production = process.env.NODE_ENV === 'production';
module.exports = {
// Webpack的入口点,指定了你的应用程序的主要入口文件
entry: {
// 多入口配置
index: path.resolve(__dirname, 'src', 'index.tsx')
},
// Webpack的输出选项,用于指定生成的 JavaScript 文件的位置和名称。
output: {
// 定义了输出的主要 JavaScript 文件的名称,它会放在dist/static/js目录下,并根据入口点的名称来生成文件
filename: 'static/js/[name].js',
// 定义了异步加载的代码块(chunks)的文件名模板。[name]表示代码块的名称,[id]是代码块的唯一标识符。这些文件会放在dist/static/js目录下
chunkFilename: 'static/js/[name].chunk.[id].js',
path: path.resolve(__dirname, 'dist'),
publicPath: '/',
},
// 配置Webpack的代码优化策略
optimization: {
// 启用了代码分割,将代码拆分成多个文件,以便更好地利用浏览器的缓存机制
splitChunks: {
// 表示所有的代码块都会被拆分
chunks: 'all',
name: false,
// 定义了一个大小阈值,当模块的大小超过这个值时,会被拆分成一个单独的文件
maxSize: 200000,
automaticNameDelimiter: '.'
},
// 这个选项将运行时的代码拆分成一个单独的文件,这可以确保在更新应用程序时,浏览器能够保持缓存的有效性,只需要下载更新的部分。
runtimeChunk: {
name: 'runtime',
},
},
// 内置优化模式,默认为production
mode: production ? 'production' : 'development',
// 控制是否生成以及如何生成source map
devtool: production ? false : 'eval-source-map',
// 开发服务器配置
devServer: {
// 静态资源配置
static: {
// 静态文件路径
directory: path.join(__dirname, 'public'),
},
// history路由必须启用,将请求重定向至index.html
historyApiFallback: true,
// 监听端口
port: 8000,
client: {
progress: true,
}
},
// 路径解析别名
resolve: {
extensions: ['.ts', '.tsx', '.js', 'jsx', '.json'],
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
// 模块处理器
module: {
rules: [
{
test: /\.ts(x?)$/,
exclude: /node_modules/,
use: ['babel-loader', 'ts-loader'],
},
{
enforce: 'pre',
test: /\.js$/,
exclude: /node_modules/,
loader: 'source-map-loader',
},
{
test: /\.((c|le)ss)$/,
oneOf: [
{
// 启用css modules时使用的loader
resourceQuery: /css_modules/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
{
loader: "css-loader",
options: {
esModule: true,
modules: {
localIdentName: "[name]-[local]"
},
url: {
// 过滤图片路径
filter: (url) => {
return !/^\/static\/\/images\//.test(url)
}
}
}
},
'postcss-loader',
'less-loader',
]
},
{
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'less-loader'
]
}
],
}
]
},
// 插件
plugins: [
new MiniCssExtractPlugin({
// 输出的每个css文件的名称
filename: 'static/css/[name].css',
// 按需加载的chunk文件名称
chunkFilename: 'static/css/[id].chunk.css',
}),
new HtmlWebpackPlugin({
title: '博客模板',
template: path.resolve(__dirname, 'public', 'index.html'),
}),
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: [path.resolve(__dirname, 'dist')]
})
]
}
常见问题
引用less
报错Cannot find module './index.less'
给less添加描述文件
declare module '*.less' {
const content: { [className: string]: string };
export default content;
}
declare module '*.less?css_modules' {
// 声明一个类型修饰符,允许带查询字符串的导入
interface WithQuery {
[key: string]: string;
}
// 导出默认的内容类型,可以是一个普通对象或一个带查询字符串的对象
const content: { [className: string]: string } & WithQuery;
export default content;
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。