[TOC]
入口起点(entry point) 指示 webpack 应该使用哪个模块,来作为构建其内部 依赖图(dependency graph) 的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。
默认值是 ./src/index.js,但你可以通过在 webpack configuration 中配置 entry 属性,来指定一个(或多个)不同的入口起点。
output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。主要输出文件的默认值是 ./dist/main.js,其他生成文件默认放置在 ./dist 文件夹中。
你可以通过在配置中指定一个 output 字段,来配置这些处理过程:
webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中。
loader 用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量
通过选择 development, production 或 none 之中的一个,来设置 mode 参数,你可以启用 webpack 内置在相应环境下的优化。其默认值为 production。
有两种形式
initial
,entry
中指定的入口chunk组non-initial
是可以延迟加载的块,可能会出现在使用动态导入(dynamic imports) 或者 SplitChunksPlugin 时每个chunk都有对应的asset,就是输出文件
webpack.config.js
entry: {
index: './src/index.js'
},
index.js
import './other.js';
console.log('I am index.js');
// 魔法字符串
import(/* webpackChunkName: "bar.js" */ './bar.js').then(res => {
console.log(res);
}, err => {
console.log(err);
});
上面的代码会创建一个名为 index
的 initial chunk
,然后 index.js
中包含other.js
还会创建一个bar.js
的non-initial chunk
,因为是import
动态导入的
编译后
生成asset js/index.js
文件
生成asset js/bar.js
文件
asset js/index.js 13.2 KiB [emitted] (name: index)
asset js/bar.js.index.js 1.17 KiB [emitted] (name: bar.js)
asset index.html 303 bytes [compared for emit]
runtime modules 6.78 KiB 9 modules
cacheable modules 517 bytes
./src/index.js 403 bytes [built] [code generated]
./src/other.js 59 bytes [built] [code generated]
./src/bar.js 55 bytes [built] [code generated]
webpack 5.82.0 compiled successfully in 322 ms
文档: https://www.webpackjs.com/api/module-methods/#magic-comments
通过在 import 中添加注释,我们可以进行诸如给 chunk 命名或选择不同模式的操作
可以给生成的 chunks 明名,如果两个 chunks 的明名相同,则最后会被打包到一个文件中
// 打包后 生成不同的 chunks 文件
import(/* webpackChunkName: "about" */'./about');
import(/* webpackChunkName: "home" */'./home');
// 打包后 会将 about.js 和 home.js 两个文件,一起打包进 other_xxx.js 文件中
import(/* webpackChunkName: "other" */'./about');
import(/* webpackChunkName: "other" */'./home');
如果只设置 webpackChunkName 魔法注释,则打包后会受到 entry 中的 filename 和 chunkFilename 的影响
设置为 true 时,禁用动态导入解析。
试了一下,原本动态导入的文件,在设置了这个后就爆粗了 😅
有好几个值比如 'lazy' 'eager' 等等,看文档吧,基本不怎么用
import()
导入的模块生成一个可延迟加载(lazy-loadable)的 chunk。告诉浏览器将来可能需要该资源来进行某些导航跳转。查看指南,了解有关更多信息 how webpackPrefetch works。
告诉浏览器在当前导航期间可能需要该资源。 查阅指南,了解有关的更多信息 how webpackPreload works。
在导入解析(import resolution)过程中,用于匹配的正则表达式。只有匹配到的模块才会被打包。
在导入解析(import resolution)过程中,用于匹配的正则表达式。所有匹配到的模块都不会被打包。
在开发环境使用import
导入没有任何问题
import(/* webpackChunkName: "indexCss" */ './style/index.css');
console.log('I am index.js');
import(/* webpackChunkName: "barJs" */ './bar.js').then(res => {
// console.log(res);
}, err => {
console.log(err);
});
在生产环境就不行了,生产的文件还是id
assets by path js/*.js 4.07 KiB
asset js/index.js 3.76 KiB [emitted] [minimized] (name: index)
asset js/857.index.js 188 bytes [emitted] [minimized] (name: barJs)
asset js/144.index.js 127 bytes [emitted] [minimized] (name: indexCss)
asset index.html 317 bytes [compared for emit]
asset css/indexCss.css 62 bytes [emitted] (name: indexCss)
...
解决:经过一番查找,发现是output
中没有设置chunkFilename
属性,设置一下就好了
output: {
path: resolve(__dirname, './dist'),
filename: 'js/index.js',
chunkFilename: 'js/[name].[contenthash:5].js',
clean: true,
},
2023.7.22号,发现没有配置 chunkFilename 也打包后,也可以改变 文件名,而且是根据 filename 的规则改变的,感觉太乱了
webpack5
npm i @babel/register --save-dev
webpack.config.js
为webpack.config.babel.js
,所有配置文件 想改为es模块的,都得改后缀为.babel.js
npm i -D @babel/core @babel/preset-env
,并配置.babelrc
{
"presets": [
"@babel/preset-env"
]
}
webpack.config.babel.js
中的配置,改为es6的语法
import webpack from 'webpack';
import path from 'path';
/**
* @type {webpack.Configuration}
*/
export default {
entry: path.resolve(__dirname, '../src/index.js'),
output: {
path: path.resolve(__dirname, '../dist'),
filename: 'index.js',
clean: true
}
}
package.json
并启动
"scripts": {
"dev": "./node_modules/webpack/bin/webpack.js --config ./config/webpack.config.babel.js --mode development"
},
文档:https://www.webpackjs.com/configuration/configuration-languages/#typescript
先下载依赖npm i -D typescript ts-node @types/node @types/webpack
如果需要用到webpack-dev-server,npm install --save-dev @types/webpack-dev-server
把webpack配置文件的文件名改为:webpack.config.ts
将webpack.config.ts
改为ts语法
import {Configuration} from 'webpack';
import path from 'path';
const devConfig: Configuration = {
entry: path.resolve(__dirname, '../src/index.js'),
output: {
path: path.resolve(__dirname, '../dist'),
filename: 'index.js',
clean: true
}
}
export default devConfig;
编写tsconfig.json
这是因为我们在前面安装的依赖ts-node不支持commonjs之外的模块语法,所以我们必须在TypeScript的配置文件tsconfig.json配置compilerOptions中module字段为:commonjs,
{
"compilerOptions": {
"module": "CommonJS",
"target": "ES5",
"esModuleInterop": true
}
}
配置package.json
"scripts": {
"dev": "./node_modules/webpack/bin/webpack.js --config ./config/webpack.config.ts --mode development",
"build": "npx webpack --config ./config/webpack.config.ts --mode production"
},
启动项目即可
可能需要重启下编辑器
获取简短的Git版本哈希
git rev-parse --short HEAD
这个迷你的包(cross-env)能够提供一个设置环境变量的scripts,让你能够以unix方式设置环境变量,然后在windows上也能兼容运行。
npm i -D cross-env
{
"scripts": {
"build": "cross-env NODE_ENV=production webpack --config build/webpack.config.js"
}
}
NODE_ENV环境变量将由cross-env设置 打印process.env.NODE_ENV === 'production'
npm i -D html-webpack-plugin
{
plugins: [
new HtmlWebpackPlugin({...})
]
}
插件的默认参数为
const defaultOptions = {
template: 'auto',
templateContent: false,
templateParameters: templateParametersGenerator,
filename: 'index.html',
publicPath: userOptions.publicPath === undefined ? 'auto' : userOptions.publicPath,
hash: false,
inject: userOptions.scriptLoading === 'blocking' ? 'body' : 'head',
scriptLoading: 'defer',
compile: true,
favicon: false,
minify: 'auto',
cache: true,
showErrors: true,
chunks: 'all',
excludeChunks: [],
chunksSortMode: 'auto',
meta: {},
base: false,
title: 'Webpack App',
xhtml: false
};
默认: 'Webpack App'
类型: string
生成的html文档的标题。配置该项,它并不会替换指定模板文件中的title元素的内容,除非html模板文件中使用了模板引擎语法来获取该配置项值,如下ejs模板语法形式
<title><%= htmlWebpackPlugin.options.title %></title>
默认: 'index.html'
类型: string | ((entryName: string) => string);
输出文件的文件名称,默认为index.html,不配置就是该文件名;此外,还可以为输出文件指定目录位置(例如'html/index.html')
link
和script
路径是相对于生成目录下的,写路径的时候请写生成目录下的相对路径。默认: auto,使用的是src/index.ejs
,如果没有则使用的是html-webpack-plugin
提供的默认模板default_index.ejs
类型: string
- 不使用任何loader的情况下,默认使用内部提供的ejs加载器解析,此加载程序不支持完整的 ejs 语法,因为它基于lodash 模板。
{
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
})
]
}
new HtmlWebpackPlugin({
// !表示所有的normal loader全部不执行(执行pre,post和inline loader)
// -!表示所有的normal loader和pre loader都不执行(执行post和inline loader)
// !! 表示所有的normal pre 和 post loader全部不执行(只执行inline loader)
// For details on `!!` see https://webpack.js.org/concepts/loaders/#inline
template: '!!handlebars-loader!src/index.hbs'
})
设置了 html-loader 就不会按照ejs加载
{
module: {
rules: [{
test: /\.html$/,
loader: 'html-loader'
}],
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
})
]
}
默认: false
类型:
templateContent?:
| false // Use the template option instead to load a file
| string
| ((templateParameters: {
[option: string]: any;
}) => string | Promise<string>)
| Promise<string>;
string | function,可以指定模板的内容,不能与template共存。配置值为function时,可以直接返回html字符串,也可以异步调用返回html字符串。
默认: false
类型:
提供一些参数给template
和templateContent
中使用
new HtmlWebpackPlugin({
// ...
templateParameters: {
title: isDev ? '开发环境_' : '生产环境_',
aa: 'aaaa'
}
// ...
}),
]
<!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><%= htmlWebpackPlugin.options.title%></title> -->
<title><%= title %></title>
</head>
<body>
<div><%= aa %></div>
</body>
</html>
为script添加defer或module,防止页面渲染阻塞
默认: defer
类型: "blocking" | "defer" | "module";
defer
的时候,在script
上添加defer
module
的时候,在script
上添加type="module"
blocking
什么也不添加如果不传递inject
的情况下,值为blocking
的时候,会将script
添加到body
后,
默认: userOptions.scriptLoading === 'blocking' ? 'body' : 'head'
类型:
inject?:
| false // Don't inject scripts
| true // Inject scripts into body
| "body" // Inject scripts into body
| "head"; // Inject scripts into head
默认 根据scriptLoading
的值来插入
向template或者templateContent中注入所有静态资源,不同的配置值注入的位置不经相同
默认: false
类型: false | string;
添加特定favicon路径到输出的html文档中,这个同title
配置项,需要在模板中动态获取其路径值
{
favicon: resolve(__dirname, '../src/img/favicon.ico')
}
默认: false
类型: boolean
给注入的script和link标签添加hash值,有助于清除缓存
<link rel="icon" href="/favicon.ico?8618a8e481136d0b9335">
<script defer src="/js/index.js?8618a8e481136d0b9335"></script>
默认: all
类型: "all" | string[];
将entry中的入口文件,插入到html中,不传值的情况下,自动将entry中的所有文件插入
也可以传一个数组,控制插入哪些入口文件
chunks: ['index', 'app'],
默认: []
类型: string[]
与chunks相反,禁止注入某些文件
excludeChunks: ['app'], // 禁止插入的文件
默认: auto
类型: "auto"|"manual"|((entryNameA: string, entryNameB: string) => number);
控制 脚本 在页面中插入的顺序 auto|manual|()=>{}
auto
与none
是一个意思,都是直接输出entry中的入口文件的顺序
在源码的chunksorter.js
中,有这么两行代码
/**
* Performs identity mapping (no-sort).
* @param {Array} chunks the chunks to sort
* @return {Array} The sorted chunks
*/
module.exports.none = chunks => chunks;
/**
* Defines the default sorter.
*/
module.exports.auto = module.exports.none;
module.exports.none = chunks => chunks;
参数chunks
就是根据entry
中的keys过滤之后的数组(根据includedChunks
和excludedChunks
)
源码如下index.js
// 获取到entry的keys
const entryNames = Array.from(compilation.entrypoints.keys());
// 过滤下 获取到entry的keys
const filteredEntryNames = filterChunks(entryNames, options.chunks, options.excludeChunks);
// 根据 chunksSortMode 传递的参数 排序
const sortedEntryNames = sortEntryChunks(filteredEntryNames, options.chunksSortMode, compilation);
function filterChunks (chunks, includedChunks, excludedChunks) {
return chunks.filter(chunkName => {
// Skip if the chunks should be filtered and the given chunk was not added explicity
if (Array.isArray(includedChunks) && includedChunks.indexOf(chunkName) === -1) {
return false;
}
// Skip if the chunks should be filtered and the given chunk was excluded explicity
if (Array.isArray(excludedChunks) && excludedChunks.indexOf(chunkName) !== -1) {
return false;
}
// Add otherwise
return true;
});
}
manual
就是按照html-webpack-plugin
中的chunks
中的顺序排序(如果是个数组),如果不是数组,就按照entry
中的keys排序
源码如下chunksorter.js
module.exports.manual = (entryPointNames, compilation, htmlWebpackPluginOptions) => {
const chunks = htmlWebpackPluginOptions.chunks;
if (!Array.isArray(chunks)) {
return entryPointNames;
}
// Remove none existing entries from
// htmlWebpackPluginOptions.chunks
return chunks.filter((entryPointName) => {
return compilation.entrypoints.has(entryPointName);
});
};
如果是一个函数,就是webpack.entry
中的 keys,过滤后,的数组 .sort
的函数
源码如下index.js
// 获取到entry的keys
const entryNames = Array.from(compilation.entrypoints.keys());
// 过滤下 获取到entry的keys
const filteredEntryNames = filterChunks(entryNames, options.chunks, options.excludeChunks);
// 根据 chunksSortMode 传递的参数 排序
const sortedEntryNames = sortEntryChunks(filteredEntryNames, options.chunksSortMode, compilation);
function sortEntryChunks (entryNames, sortMode, compilation) {
// Custom function 如果是个函数
if (typeof sortMode === 'function') {
return entryNames.sort(sortMode);
}
// Check if the given sort mode is a valid chunkSorter sort mode
if (typeof chunkSorter[sortMode] !== 'undefined') {
return chunkSorter[sortMode](entryNames, compilation, options);
}
throw new Error('"' + sortMode + '" is not a valid chunk sort mode');
}
默认: false
类型: boolean
是否渲染link
为自闭合的标签,true则为自闭合标签
默认: true
类型: boolean
如果为true表示在对应的thunk文件修改后就会emit文件
默认: true
类型: boolean
错误详细信息将写入 HTML 页面
默认: auto
类型: "auto" | boolean | MinifyOptions;
压缩html
默认: auto
类型: publicPath?: string | "auto";
压缩html
{
/*
By default the public path is set to `auto` - that way the html-webpack-plugin will try
to set the publicPath according to the current filename and the webpack publicPath setting
默认情况下,公共路径设置为“auto”,这样html webpack插件就会尝试根据当前文件名和webpack publicPath设置设置publicPath
*/
publicPath: 'aa/bb/cc',
}
不设置的情况下,默认为auto,会自动的根据webpack的publicPath生成路径,如果设置其他的字符串,就会在注入的js、img、css等路径前拼接上该字段。
<link rel="icon" href="aa/bb/cc/mouse_cat_1.png"></head>
<script src="aa/bb/cc/js/index.js"></script></body>
类型:
meta?:
| false // Disable injection
| {
[name: string]:
| string
| false // name content pair e.g. {viewport: 'width=device-width, initial-scale=1, shrink-to-fit=no'}`
| { [attributeName: string]: string | boolean }; // custom properties e.g. { name:"viewport" content:"width=500, initial-scale=1" }
};
生成meta标签
{
// ...
meta: {
// 生成 <meta charset="UTF-8">
charset: {
charset: 'UTF-8' // 指定文档使用的字符集
},
// 生成 <meta name="keywords" content="webpack,webpack.config.ts">
keywords: 'webpack,webpack.config.ts', // 定义网页的关键词,是一种用于 SEO 的技术;
description: 'html-webpack-plugin练习', // 描述网页的内容,对搜索引擎优化(SEO)和用户体验有帮助;
viewport: 'width=device-width, initial-scale=1.0',
author: 'qxy',
expires: {
'http-equiv': 'expires',
content: '31 Dec 2008'
},
'content-type': {
'http-equiv': 'Content-Type',
content: 'text/html; charset=utf-8'
},
refresh: {
'http-equiv': 'refresh',
content: '1000; url=https://www.baidu.com'
}
},
// ...
}
new HtmlWebpackPlugin({
title: 'xxxhtml',
filename: 'index.html',
template: resolve(__dirname, '../src/index.ejs'),
inject: 'body',
favicon: resolve(__dirname, '../src/img/favicon.ico')
})
在引入html-webpack-plugin
时,ts类型报错
在tsconfig.json
中设置
{
"compilerOptions": {
"esModuleInterop": true // 用于解决es模块和commonjs模块互相操作的问题,否则es模块在操作commonjs模块时,会因为语法不同而出问题,开启这个选项,ts会自动生成额外的代码,将commonjs模块转换成es模块
}
}
一般和css-loader
一起使用,css-loader
将css文件样式转成可执行的commonjs模块,然后style-loader
将可执行的commonjs 转变成插入html中的style标签
安装: npm i --save-dev style-loader css-loader
默认: styleTag
类型: styleTag(默认值)、
singletonStyleTag、
autoStyleTag、
lazyStyleTag、
lazySingletonStyleTag、
lazyAutoStyleTag、
linkTag
styleTag: 通过使用多个 <style></style>
自动把 styles 插入到 DOM 中,即一个 import
就会生成一个 style
标签,无论是js还是css中引入的
import './style/index.css';
import './style/a.css';
<style>...</style>
<style>...</style>
singletonStyleTag: 对应 styleTag
,不过它是把他们整合到一个 style
标签中,如有重复的样式名不会额外处理(Source map 不起作用)
import './style/index.css';
import './style/a.css';
<style>...</style>
autoStyleTag: 与styleTag
相同,但是当代码在 IE6-9 中运行时,请打开singletonStyleTag
模式。
lazyStyleTag: 推荐 lazy style 遵循使用 .lazy.css
作为后缀的命名约定,style-loader
基本用法是使用 .css
作为文件后缀(其他文件也一样,比如:.lazy.less
和 .less
)。
当使用 lazyStyleTag
时,style-loader
将惰性插入 styles,在需要使用 styles 时可以通过 style.use()
/ style.unuse()
使 style 可用。
import styles from "./styles.lazy.css";
styles.use();
// 要移除 styles 时你可以调用
// styles.unuse();
同时.css
文件和.lazy.css
文件,要分开成两个来处理
{
test: /\.css$/,
exclude: /\.lazy\.css$/i,
use: ['style-loader', 'css-loader']
},
{
test: /\.lazy\.css$/i,
use: [
{ loader: "style-loader", options: { injectType: "lazyStyleTag" } },
"css-loader",
],
},
lazySingletonStyleTag: 文档上说是问lazyStyleTag
效果类似,只是最后会生成一个style
标签,但是试了一下,还是多个style
标签(Source maps 不起作用)
lazyAutoStyleTag: 与lazyStyleTag
相同,但是当代码在 IE6-9 中运行时,请打开lazySingletonStyleTag
模式。
linkTag: 使用多个 <link rel="stylesheet" href="path/to/file.css">
将 styles 动态的插入到 DOM 中。
只不过需要配合file-loader
,静态插入请使用MiniCssExtractPlugin
{
test: /\.css$/,
exclude: /\.lazy\.css$/i,
use: [
isProduction
? 'style-loader'
: {
loader: 'style-loader',
options: {
// styleTag(默认值)、singletonStyleTag、autoStyleTag、lazyStyleTag、lazySingletonStyleTag、lazyAutoStyleTag、linkTag
injectType: 'linkTag'
}
},
'file-loader'
]
},
默认: {}
类型: Object
{
loader: 'style-loader',
options: {
// styleTag(默认值)、singletonStyleTag、autoStyleTag、lazyStyleTag、lazySingletonStyleTag、lazyAutoStyleTag、linkTag
// 插入 style标签的方式
injectType: 'singletonStyleTag',
// 自定义添加style标签上的属性
attributes: {
aa: 'aa'
}
}
}
<style aa="aa">...</style>
默认: head
类型: String|Function
style
标签插入的位置,默认是head标签的尾部,因为是尾部会比原本就存在html中的style
标签的优先级高。
如果是String类型的,会通过query selector找到这个标签,然后插入
也可以是一个函数,自定义插入的位置,详情见官网
如果是一个函数,函数的第一个参数应该是style
标签元素,第二个参数可以接收到,injectType
为lazyStyleTag|lazySingletonStyleTag
时,use
函数传递过来的参数,详情请查阅官网
默认值:undefined
类型:String | Function
当将 'style' 标签插入到 DOM 中时,转换标签和 css。
如果是一个函数,函数的第一个参数是 css字符串
,第二个参数是插入的style标签
,然后就可以对css字符串
进行操作
貌似injectType
为singletonStyleTag
的时候,函数不执行
{
test: /\.css$/,
exclude: /\.lazy\.css$/i,
use: [
isProduction
? 'style-loader'
: {
loader: 'style-loader',
options: {
// 将style插入html的时候,可以进项一些操作
styleTagTransform: function(css, style) {
// 在浏览器上打印查看,因为只有的执行js的情况下,才会将css插入到dom
console.log(111, css, style);
style.innerHTML = `${css} h1 {font-weight: normal;}\n`;
document.body.appendChild(style);
}
}
},
'css-loader'
]
},
不清楚
默认: true
类型: Boolean
默认情况下,style-loader
生成使用 ES 模块语法的 JS 模块。在某些情况下使用 ES 模块语法更好,比如:module concatenation 和tree shaking 时。
一般情况下不需要其他的配置
在开发环境下推荐使用style-loader
,style标签
直接插入到DOM中,运行的更快
在生产环境下推荐使用mini-css-extract-plugin
,将CSS提取成单独的文件,以便CSS和JS可以在浏览器并行加载
不要将 style-loader
于 mini-css-extract-plugin
一起使用。
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const devMode = process.env.NODE_ENV !== "production";
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
isProduction ? "style-loader" : MiniCssExtractPlugin.loader,
"css-loader"
],
},
],
},
plugins: [].concat(isProduction ? [] : [new MiniCssExtractPlugin()]),
};
style-loader好像并不能单独的使用,需要配合css-loader使用
先使用css-loader
处理css文件
const style = import('./style/index.css');
style.then(res => {
console.log(res);
}, err => {
console.log(err);
})
打印出
{
"default": [
[
"./src/style/index.css",
"/* * {\n box-sizing: border-box;\n} */\n\n.title {\n color: red;\n}",
""
]
]
}
然后使用style-loader
利用js代码插入一个style标签到页面中
<style>/* * { box-sizing: border-box; } */ .title { color: red; }</style>
内容安全策略(Content Security Policy,CSP)是一种用于保护 Web 应用程序免受跨站点脚本攻击(XSS)等攻击的技术。CSP 规定了哪些资源可以被加载和执行,从而限制了攻击者可以注入的恶意代码。
attributes 方式:
style标签设置属性 attributes.nonce: '1234'
meta标签设置属性 <meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'nonce-1234'">
module: {
rules: [
{
test: /.css$/,
exclude: /node_modules/,
use: [
{
loader: 'style-loader',
options: {
attributes: {
nonce: '1234'
},
}
},
{
loader: 'css-loader',
options: {
sourceMap: true
}
}
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
title: isDev ? '开发环境' : '生产环境',
template: './src/index.html',
filename: 'index.html',
meta: {
httpEquiv: {
'http-equiv': 'Content-Security-Policy',
content: "default-src 'self'; style-src 'nonce-1234'"
}
}
})
]
css-loader
会对 @import
和 url()
进行处理,就像 js 解析 import/require()
一样。
安装: npm install --save-dev css-loader
文档: https://github.com/webpack-contrib/css-loader
文档:https://github.com/webpack-contrib/css-loader#url
默认值: true
类型:
type url =
| boolean
| {
filter: (url: string, resourcePath: string) => boolean;
};
允许启用/禁用处理 CSS 函数url
和image-set
. 如果设置为false
h1 {
color: red;
background: url('~@/img/cat.webp') no-repeat;
}
=>
h1 {
color: red;
background: url(file:///Users/v_qianxinyu/Desktop/qxy-fe/webpack-study/webpack-3/webpack-exercise/dist/img/cat.bc934.webp) no-repeat;
}
如果路径中使用了别名,需要加上前缀~
h1 {
color: red;
background: url('~@/img/cat.webp') no-repeat;
}
补充:image-set
和srcset
的作用,都是为不同分辨率的设备匹配图像
image-set: 文档
srcset: 文档
默认: true
类型:
type import =
| boolean
| { filter: (url: string, media: string, resourcePath: string) => boolean };
是否解析 @import 和 @import url() 的样式导入
默认: undefined
类型: 详见文档,太多了
是否启用模块化
const cssLoader = {
loader: 'css-loader',
options: {
// modules: true, // 开启模块化
modules: {
// 默认 local
//
// `local`(默认值): `CSS Modules` 会默认开启局部作用域,所有**全局变量**都要加上 `:global`前缀;
// `global`: `CSS Modules` 会默认开启全局作用域,所有**局部变量**都要加上 `:local`前缀;
// `pure`: 选择器必需包含至少一个局部 class 或者 id;
// `icss`:`icss` 只会编译低级别的`Interoperable CSS` 格式,用于声明 CSS 和其他语言之间的 `:import `和 `:export`依赖项。
//
// mode: "local",
mode: (resourcePath) => {
console.log(1111, resourcePath);
if (/(\/src\/styles|\/node_modules\/)/i.test(resourcePath)) {
return "global";
}
return "local";
},
// 默认 undefined
//
// undefined- 为所有文件启用 CSS 模块。
// true- 为所有匹配的文件 正则表达式启用 CSS 模块
// false- 禁用 CSS 模块
// RegExp- 为所有匹配/RegExp/i.test(filename)正则表达式的文件启用 CSS 模块。
// function- 根据满足过滤器功能检查的文件名为文件启用 CSS 模块。
//
// auto 因为正则的默认值,文件中有 .module. 的会自动当成模块化处理,即使不手动配置modules
// auto: (resourcePath) => {
// // 表示 文件名为 .global.less|css 的文件不开启模块化
// return !(resourcePath.endsWith(".global.css") || resourcePath.endsWith(".global.less"))
// },
// 默认 false 允许css-loader从全局类或 id 中导出名称,因此您可以将其用作本地名称(不懂)
// exportGlobals: true,
// 生成类名的格式
// 默认 [hash:base64]
//
// 开发建议: [path][name]__[local]--[hash:base64:5]
// 生产建议:[hash:base64]
//
localIdentName: "[path][name]__[local]--[hash:base64:5]",
// 默认 compiler.context 允许为本地标识名称重新定义基本加载程序上下文。
// localIdentContext: path.resolve(__dirname, "src"),
// 默认:undefined 允许添加自定义哈希以生成更多独特的类
// 自定义生成 hash的规则
// localIdentHashSalt: undefined,
// namedExport、exportLocalsConvention 设置用户在js中使用类名的方式
// 默认:false 为本地人启用/禁用名为 export 的 ES 模块。
// 意思就是 是否可以在js中 以结构赋值的使用方式获取类名,此时 exportLocalsConvention 的值必须是 camelCaseOnly
// 如果为true的话,可以这样获取类名 import {app} from './index.module.less'
namedExport: false,
// 在js中的 使用方式 'camelCase' 表示 既可以驼峰使用,也可以字符串使用
// 可以在导入css文件时,查看导入的对象中 有几种格式
//
// 'asIs' 类名将按原样导出。
// 'camelCase' 类名将被骆驼化,原始类名不会从本地人中删除
// 'camelCaseOnly' 类名将被驼峰化,原来的类名将从本地人中删除
// 'dashes' 只有类名中的破折号会被驼峰化
// 'dashesOnly' 类名中的破折号将被驼峰化,原始类名将从本地人中删除
//
exportLocalsConvention: "camelCase",
// 默认:false 不是预渲染(ssr),可以关掉
exportOnlyLocals: false,
},
// postcss-loader 文档说,想要正常工作要给css-loader加上这个属性
importLoaders: 2
}
};
默认: true
类型: type esModule = boolean;
将css文件,解析成es模块语法的js代码,设置为false,就是commonjs语法
默认: array
类型: type exportType = "array" | "string" | "css-style-sheet";
不懂,应该是将css文件转换成js后,导出的形式,'array'(数组) | 'string'(字符串) | 'css-style-sheet'(样式对象)
导出后,给下一个loader使用,比如style-loader,我猜的
默认是跟 webpack配置项 中的 devtool 保持一致
类型:Number
默认:0
importLoaders
选项允许你配置在 css-loader
之前有多少 loader 应用于 @import
ed 资源与 CSS 模块/ICSS 导入。
从右往左数
自己写的话,一般不用配置,直接使用即可
// webpack.config.js
{
module: {
rules: [
{
test: /\.css$/,
// 不处理文件名包含有 node_modules 和 .global.css 的文件
exclude: /(node_modules|\.global\.css$)/,
use: [
// 生产环境下使用 mini-css-extract-plugin,开发环境下使用 style-loader
isProduction ? miniCssExtractLoader : styleLoader,
{
loader: 'css-loader',
options: {
modules: {
// 所有的css文件,开启局部作用域
mode: 'local',
// 生成局部类名的格式
localIdentName: isProduction ? '[hash:base64]' : '[path][name]__[local]--[hash:base64:5]',
// 自动转驼峰,并且不删除原有的类名
exportLocalsConvention: 'camelCase'
},
}
},
]
},
{
// 文件名中包含 .global.css 的为全局css
test: /\.global\.css$/,
exclude: /(node_modules)/,
use: [
// 生产环境下使用 mini-css-extract-plugin,开发环境下使用 style-loader
isProduction ? miniCssExtractLoader : styleLoader,
'css-loader'
]
},
]
}
}
<h1 class="title">
<div class="abc"><%= title %></div>
</h1>
/* index.css */
/* 局部css */
.title {
color: red;
background: url('../../../../static/mouse_cat_1.png');
}
.h1-title {
font-size: 22px;
/* 在本地作用域中,给全局作用域的类名设置样式 */
:global .abc {
border: 10px solid black;
}
}
/* style.global.css */
/* 全局 */
* {
box-sizing: border-box;
}
.flex {
display: flex;
}
.align-items-center {
align-items: center;
}
.justify-content-center {
justify-content: center;
}
// index.js
import './style/style.global.css'; // 全局css
import style from '@/style/index.css'; // 局部css
console.log(style);
const title = document.querySelector('.title');
title.classList.add(style.title) // 局部css添加类名
title.classList.add(style.h1Title)
title.classList.add('flex') // 全局css添加类名
title.classList.add('justify-content-center')
将less转换成css
安装: npm install --save-dev less-loader
less-loader
会一起安装less
包,也可以手动在安装一次npm i -D less
{
test: /\.less/,
use: [
isProduction ? 'style-loader' : 'style-loader',
'css-loader',
'less-loader'
]
},
就是传入一些less的options
export const lessLoader = {
loader: 'less-loader',
options: {
// 定义一些less的配置
lessOptions: {
// 比如这个,定义less的全局变量,在样式中,直接 @dark、@red 使用
globalVars: {
dark: '#000',
red: '#f00'
}
}
}
}
入口文件的起始位置添加 Less 代码
export const lessLoader = {
loader: 'less-loader',
options: {
// 可以添加一些变量,入口文件的起始位置添加 Less 代码,在代码中可以直接使用 @font-size-14
// 也可以是一个函数,更加方便的决定添加变量的位置
// 详见文档 https://webpack.docschina.org/loaders/less-loader/#additionaldata
additionalData: `@font-size-14: 14px;`,
}
}
类型: Boolean
默认值: 取决于 compiler.devtool
的值
默认生成的 source map 取决于 compiler.devtool
的值。除了值等于 eval
和 false
外,其他值都能生成 source map
启用/禁用 webpack 默认的 importer。
特殊的 implementation
选项决定使用 Less 的哪个实现。重载本地安装的 less
的 peerDependency
版本。
此选项只对下游的工具作者有效,以便于 Less 3 到 Less 4 的过渡。
export const lessLoader = {
loader: 'less-loader',
options: {
sourceMap: true, // 简单设置即可
}
}
将sass转换成css
安装: npm install --save-dev sass sass-loader
直接安装sass-loader
会自动的安装上sass
,如果想要控制sass的版本,在单独安装sass
也行,安装sass
就相当于是安装了Dart Sass
,webpack文档上推荐使用Dart Sass
文档
{
test: /\.less/,
use: [
isProduction ? 'style-loader' : 'style-loader',
'css-loader',
'sass-loader'
]
},
选项确定要使用的 Sass 实现,sass 或 node-sass
import {LoaderOptions} from 'sass-loader/interfaces';
export const sassLoader = {
loader: 'sass-loader',
options: {
// 选项确定要使用的 Sass 实现,sass 或 node-sass
// 默认sass,会自动的读取package.json中安装了 sass还是node-sass,因此安装sass-loader的时候,最好也一起安装sass
implementation: 'sass'
} as LoaderOptions
}
与lessOptions
类似,就是设置sass的配置项,Dart Sass和Node-Sass配置项不同
import {LoaderOptions} from 'sass-loader/interfaces';
export const sassLoader = {
loader: 'sass-loader',
options: {
// 设置sass的配置项
sassOptions: {
// 用于确定用于缩进的空格或制表符的数量。
indentWidth: 4,
// 用于确定是使用空格还是制表符进行缩进。
indentType: 'space'
}
} as LoaderOptions
}
如果为 true 将会忽略来自 sassOptions 的 sourceMap,sourceMapRoot,sourceMapEmbed,sourceMapContents 和 omitSourceMapUrl 选项。
import {LoaderOptions} from 'sass-loader/interfaces';
export const sassLoader = {
loader: 'sass-loader',
options: {
// 如果为 true 将会忽略来自 sassOptions 的 sourceMap,sourceMapRoot,sourceMapEmbed,sourceMapContents 和 omitSourceMapUrl 选项。
sourceMap: true,
} as LoaderOptions
}
在实际的文件之前要添加的 Sass
/ SCSS
代码。 在这种情况下,sass-loader
将不会覆盖 data
选项,而只是将它拼接在入口文件内容之前。
总是报错没成功过。。。
开启 / 关闭默认的 Webpack importer。
将 @warn
规则视为 webpack 警告。
import {LoaderOptions} from 'sass-loader/interfaces';
export const sassLoader = {
loader: 'sass-loader',
options: {
sourceMap: true
} as LoaderOptions
}
PostCSS 是一个允许使用 JS 插件转换样式的工具。 这些插件可以检查(lint)你的 CSS,支持 CSS Variables 和 Mixins, 编译尚未被浏览器广泛支持的先进的 CSS 语法,内联图片,以及其它很多优秀的功能。
安装: npm install --save-dev postcss-loader postcss
如果你在 JS 文件中编写样式,请使用 postcss-js
parser,并且添加 execute
选项。
允许设置 PostCSS 选项
和插件。
类型:Boolean
默认值:依赖于 compiler.devtool
的值
特殊的 implementation
选项决定使用 PostCSS 哪个实现。重载本地安装的 postcss
的 peerDependency
版本。
这里简单的设置下,可以查看另一篇,postcss学习,其中有些插件的用法并不适合在webpack中使用,谨慎
首先一定要设置需要兼容的浏览器版本
可以在package.json
或者 .browserslistrc
或者 postcss.config.js
或者 webpack.config.ts
中设置,个人比较喜欢在package.json
中设置,因为其他包可以共享此设置了。
package.json 中
{
"browserslist": [
"last 5 versions",
"> 0.1%",
"not dead",
"ie>=8"
]
}
.browserslistrc 中
"last 5 versions",
"> 0.1%",
"not dead",
"ie>=8"
postcss.config.ts 中
import postcssPresetEnv from 'postcss-preset-env';
module.exports = {
plugins: [
postcssPresetEnv({
browsers: [
"last 5 versions",
"> 0.1%",
"not dead",
"ie>=8"
]
})
]
}
webpack.config.ts 中
import postcssPresetEnv from 'postcss-preset-env';
export const postcssLoader = {
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
[
// 在postcss.config.js中配置postcss比较好,在loader中配置容易有坑
postcssPresetEnv({
// preserve: true,
browsers: [
"last 5 versions",
"> 0.1%",
"not dead",
"ie>=8"
]
})
]
]
}
}
}
然后在设置插件,比如 postcss-preset-env
,cssnano
,stylelint
等等,对css使用的插件,基本就是自动添加前缀、压缩css、校验格式等。
postcss-preset-env
将css转换为大多数浏览器可以识别的,并根据目标浏览器或运行时环境确定所需的polyfill。已经包含了autoprefixer
的功能,因此不用再设置autoprefixer
了cssnano
压缩css,一般使用webpack
插件css-minimizer-webpack-plugin
,内部默认也是使用的cssnano
,详见下方css-minimizer-webpack-plugin
与cssnano
stylelint
校验css格式的插件,在webpack
中使用stylelint-webpack-plugin
,详见下方stylelint
上面这样看来,有些原本直接在postcss中使用的插件,并不适合在webpack中直接使用,需要找相应的webpack插件,因此postcss在webpack中的插件配置就简单了许多,如下:
推荐在postcss.config.js中配置postcss,在postcss-loader中遇到很多的坑,因此就放弃在postcss-loader中配置了
上面说的不适合不太正确,是因为我在postcss-loader中配置的时候,代码格式写错了,导致的运行结果异常,postcss的配置既可以在配置文件中配置,也可以在postcss-loader中配置,看情况而定。
// postcss.config.js
const {pluginOptions} = require('postcss-preset-env');
module.exports = {
plugins: [
// https://github.com/csstools/postcss-preset-env
[
'postcss-preset-env',
/** * @type {pluginOptions} */ ({
// preserve: true,
})
],
// https://cssnano.co/docs/introduction/
// 'cssnano', // 感觉不如 css-minimizer-webpack-plugin 好用,就不用这个了
]
}
or
// postcss-loader
import postcssPresetEnv from 'postcss-preset-env';
import cssnano from 'cssnano';
const postcssLoader = {
loader: 'postcss-loader',
options: {
// implementation: require('postcss'),
sourceMap: true,
postcssOptions: {
plugins: [
// 这样写 属性 有语法提示,比较友好
postcssPresetEnv({
// preserve: true,
// 在 package.json 或 .browserslistrc 中设置即可
// browsers: [
// "last 5 versions",
// "> 0.1%",
// "not dead",
// "ie>=8"
// ]
}),
cssnano(),
]
}
}
}
css-minimizer-webpack-plugin
与cssnano
如果使用了
css-minimizer-webpack-plugin
,就可以不用设置cssnano
,因为css-minimizer-webpack-plugin
默认就是使用的cssnano
压缩的css
webpack中推荐使用css-minimizer-webpack-plugin
下面的问题是本人粗心大意代码格式写错,搞出的问题,先留着,万一又遇到了呢
如果在postcss-loader中直接设置cssnano,会出现一些问题,比如压缩不生效,自动加前缀不生效等等,推荐在postcss.config.js中设置postcss的配置项,就没有这些问题
需要放在 postcss-preset-env 插件之前才生效,具体可能原因如下:
将cssnano插件放在postcss-preset-env插件之前是因为这样可以确保cssnano在postcss-preset-env之前对CSS进行压缩和优化。具体来说,postcss-preset-env会根据指定的浏览器兼容性列表自动添加浏览器前缀、转换CSS新特性,并通过一些polyfill满足某些浏览器不支持的特性。这个过程会涉及到对CSS的重写和添加,可能会造成cssnano无法正确地识别CSS代码,因此必须将cssnano插件放在postcss-preset-env插件之前,确保在添加浏览器前缀和转换新特性之前对CSS进行压缩和优化。
另外,需要注意的是,虽然将cssnano插件放在postcss-preset-env插件之前可以解决一些问题,但在实际开发中,还需要根据具体情况进行优化和调整。例如,如果使用了其他的插件对CSS进行了复杂的处理,那么可能需要进一步调整插件的顺序和配置,以做到最佳的优化效果。
虽然压缩生效了,但是postcss-preset-env的一些功能却失效了,比如添加前缀
一个常见的例子就是自动添加浏览器前缀的功能。在postcss-preset-env插件处理CSS之前,cssnano插件可能已经对CSS进行了压缩和优化,但是其中可能包含了一些不兼容的CSS代码,需要通过postcss-preset-env插件进行转换和添加浏览器前缀。但由于cssnano插件已经对这些CSS代码进行了处理,postcss-preset-env插件可能会错过这些代码,导致浏览器兼容性问题。
解决,不使用cssnano插件,使用其他压缩css的插件
// postcss-loader 配置
import postcss from 'postcss';
import postcssPresetEnv from 'postcss-preset-env';
import cssnano from 'cssnano';
export const postcssLoader = {
loader: 'postcss-loader',
options: {
implementation: postcss, // 不经常设置,第三方的库可能会设置 less-loader、sass-loader 等都是
sourceMap: true,
// 在postcss.config.js中配置postcss比较好,在loader中配置容易有坑
postcssOptions: {
plugins: [
[
// cssnano(),
// 这样写 属性 有语法提示,比较友好
postcssPresetEnv(),
]
]
},
}
}
使用 css-minimizer-webpack-plugin
// webpack.config.ts
import CssMinimizerWebpackPlugin from 'css-minimizer-webpack-plugin';
export default = {
// ...
optimization: {
minimizer: [
// 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`),将下一行取消注释
`...`,
// 生产模式下 压缩css
// TODO 很奇怪,在文档上看是要写在这儿的,但是开发模式下即使minimize: true,也不生效,写在plugin中就没有这个问题
new CssMinimizerWebpackPlugin(),
],
// !坑,如果需要开发模式下也压缩,在设置这个属性,否则不要乱设置,可能会造成压缩失效
// minimize: true, // true 开发模式下也压缩
},
}
postcss-loader 在rules中配置
// webpack.config.js
import postcss from 'postcss';
export const postcssLoader = {
loader: 'postcss-loader',
options: {
implementation: postcss, // 不经常设置,第三方的库可能会设置 less-loader、sass-loader 等都是
sourceMap: true,
}
配置.browserslistrc,一定要配置,否则有些功能不起作用
last 5 versions
> 0.01%
not dead
使用css新特性,自动添加前缀,也可以在postcss-loader中配置
// postcss.config.js
const {pluginOptions} = require('postcss-preset-env');
const tailwindcss = require('tailwindcss');
module.exports = {
plugins: [
// tailwindcss ui库的配置,不需要直接删掉即可,tailwindcss 还需要 tailwind.config.js 配置文件
// https://tailwindcss.com/docs/using-with-preprocessors#nesting
'tailwindcss/nesting',
// https://tailwindcss.com/docs/installation
tailwindcss(),
// https://github.com/csstools/postcss-preset-env
[
'postcss-preset-env', // 使用css的新特性,自动添加前缀等等
/** * @type {pluginOptions} */ ({
// preserve: true,
})
],
// https://cssnano.co/docs/introduction/
// 'cssnano', // 感觉不如 css-minimizer-webpack-plugin 好用,就不用这个了
]
}
压缩css
// webpack.config.js
import CssMinimizerWebpackPlugin from 'css-minimizer-webpack-plugin';
module.exports = {
// ...
optimization: {
minimizer: [
// 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`),将下一行取消注释
`...`,
// 生产模式下 压缩css
// TODO 很奇怪,在文档上看是要写在这儿的,但是开发模式下即使minimize: true,也不生效,写在plugin中就没有这个问题
new CssMinimizerWebpackPlugin(),
],
// !坑,如果需要开发模式下也压缩,在设置这个属性,否则不要乱设置,可能会造成压缩失效
// minimize: true, // true 开发模式下也压缩
},
}
校验css格式,参考下面的 stylelint
在webpack中使用stylelint,需要借助stylelint-webpack-plugin
插件
stylelint-webpack-plugin
插件实际上也是调用的stylelint.lint(options)
的api进行校验的,因此插件的参数跟stylelint.lint
的参数基本一致。在此只写了在webpack中如何使用
stylelint官网: https://stylelint.io/
stylelint中文文档: http://stylelint.docschina.org/user-guide/ 有点旧
stylelint-webpack-plugin文档: https://webpack.docschina.org/plugins/stylelint-webpack-plugin/
stylelint github: https://www.npmjs.com/package/stylelint
stylelint-config-standard: https://www.npmjs.com/package/stylelint-config-standard
awesome stylelint: 很多插件配置的列表 https://github.com/stylelint/awesome-stylelint
推荐笔记
首先要安装 stylelint stylelint-webpack-plugin stylelint-config-standard
等等需要用到的插件
项目根目录下创建 stylelint.config.js 配置文件
webpack 中配置 stylelint-webpack-plugin 插件
安装 stylelint vscode 插件,Stylelint
// 需要再vscode中配置
// stylelint
"stylelint.validate": ["css", "less", "scss", "sass", "stylus"],
// 设置为true,保存是自动格式化
"editor.codeActionsOnSave": {
"source.fixAll.stylelint": false, // stylelint 的配置
},
npm install stylelint-webpack-plugin stylelint --save-dev
优先级从上到下
package.json
中的 stylelint
属性.stylelintrc
文件.stylelintrc.{cjs,js,json,yaml,yml}
文件stylelint.config.{cjs,mjs,js}
文件可以通过在项目根目录创建一个 .stylelintignore 文件告诉 stylelint 去忽略特定的文件和目录。.stylelintignore 文件是一个纯文本文件,其中的每一行都是一个 glob 模式表明哪些路径应该忽略检测。(默认忽略node_modules)
globs 匹配使用 node-ignore: https://github.com/kaelzhang/node-ignore
常用的也就没几项,比如rules、extends、plugins
规则列表: https://stylelint.io/user-guide/rules/list
可以继承其他已经配置好的stylelint的配置,比如官方推荐的配置stylelint-config-standard
值可以是npm包,也可以是一个相对/绝对路径
{
"extends": "stylelint-config-standard",
"rules": {
// 可以修改一些规则
}
}
用于自定义一些插件,比如有些类css语法,sass/scss/less等预处理器的语法跟css还是不一致的,css的校验规则可能并不能完好的校验,因此就需要引用一些插件,帮助校验。
awesome-stylelint: https://github.com/stylelint/awesome-stylelint#plugins
值可以是npm包,也可以是一个相对/绝对路径
{
"plugins": ["stylelint-scss"],
"rules": {
"scss/dollar-variable-pattern": "^foo",
"scss/selector-no-redundant-nesting-selector": true
}
}
指定要在代码中使用的自定义语法
const lessSyntax = require('postcss-less');
const scssSyntax = require('postcss-scss');
module.exports = {
extends: ['stylelint-config-standard'/* , '@ecomfe/stylelint-config', */ /* , './config/stylelint.js' */],
overrides: [
{
files: ['**/*.css'],
},
{
files: ['**/*.less'],
customSyntax: lessSyntax, // npm i -D postcss-less 识别除了css以外的,类似css的语法,比如less/sass等
},
{
files: ['**/*.scss'],
customSyntax: scssSyntax, // npm i -D postcss-scss
},
],
}
修改未设定警告级别的规则的默认警告级别
{
"defaultSeverity": "warning"
}
const lessSyntax = require('postcss-less');
const scssSyntax = require('postcss-scss');
module.exports = {
extends: ['stylelint-config-standard'/* , '@ecomfe/stylelint-config', */ /* , './config/stylelint.js' */],
overrides: [
{
files: ['**/*.css'],
},
{
files: ['**/*.less'],
customSyntax: lessSyntax,
rules: [
// ...
]
},
],
rules: [
// ...
]
}
置忽略检查的文件列表,node_modules为默认默认忽略目录,同样的还可以通过.stylelintignore去配置
{
"ignoreFiles": ["**/*.js"]
}
设置是否允许注释配置
{
"ignoreDisables": true
}
stylelint.config.js
// import lessSyntax from 'postcss-less'; // 不识别es模块
const lessSyntax = require('postcss-less');
const scssSyntax = require('postcss-scss');
// ./config/stylelint.js 中的配置就是 @ecomfe/stylelint-config 中的配置
module.exports = {
// 有一些已经写好的 第三方校验规则,可以通过extends使用,可以是一个包名称,也可以直接引入文件路径
extends: ['stylelint-config-standard'/* , '@ecomfe/stylelint-config', */ /* , './config/stylelint.js' */],
overrides: [
{
files: ['**/*.css'],
},
{
files: ['**/*.less'],
customSyntax: lessSyntax, // npm i -D postcss-less 识别除了css以外的,类似css的语法,比如less/sass等
},
{
files: ['**/*.scss'],
customSyntax: scssSyntax, // npm i -D postcss-scss
},
],
rules: {
// 选择器伪类无未知
'selector-pseudo-class-no-unknown': [
true,
{
ignorePseudoClasses: ['global'],
},
],
// 不允许未知的属性值
'declaration-property-value-no-unknown': [
true,
{
ignoreProperties: [],
}
],
/*
颜色
*/
// 颜色值缩写
'color-hex-length': 'short',
// 限制单行声明块中的声明数量
'declaration-block-single-line-max-declarations': 1,
}
};
// webpack.config.js
{
plugins: [
// ...
// stylelint检测css
// https://stylelint.io/user-guide/options#allowemptyinput,参数就是stylelint.lint()的参数
new StylelintWebpackPlugin({
// 当glob模式不匹配任何文件时,Stylelint不会抛出错误。
// allowEmptyInput: false,
// 指定 stylelint 配置文件的位置。一般不用设置,由stylelint去查找配置文件
// configFile: './stylelint.config.js', // 默认值:undefined
// 指定文件根目录的字符串。
// context: resolve(__dirname, s'./src'), // 默认值:compiler.context
// 指定要忽略的文件或目录。必须相对于 options.context。
// exclude: [], // 默认值:['node_modules', compiler.options.output.path]
// 指定要检查的扩展名。
// extensions: ['scss'], // 默认值:['css', 'scss', 'sass']
// 可指定为目录,文件名或 globs。目录会递归地寻找与 options.extensions 相匹配的文件。文件名和 glob 模式将忽略 options.extensions。
files: ['**/*.scss'], // 默认值:'**/*.(s(c|a)ss|css)'
// code: '', // 一段css代码,files和code二选一
// 指定要在代码中使用的自定义语法。
// customSyntax: 'postcss-less',
// 如果值为 true,stylelint 将修复尽可能多的 error。修复真实的源文件。报告所有未修复的 errors。请参阅 自动修复错误 文档。
fix: false, // 默认值:false,一般都是false,需要手动修改
// 指定要用于格式化结果的 formatter。请参阅 formatter 选项。
formatter: 'string', // 默认值:'string'
// 设置为 true 时,会根据 cpu 的数量自动决定池子的大小。设置为大于 1 的数字时,可以确定池子的大小。设置为 false、1 或更小时,会禁用池子,并只在主线程运行。
threads: true, // 默认值:false
cache: true, // 开启缓存
cacheLocation: resolve(__dirname, './node_modules/.cache/stylelint-cache'), // 缓存保存目录
// 仅检查有变化的文件,启动时跳过检查。
// lintDirtyModulesOnly: true, // 默认值:false
// 如遇到错误将会被直接输出,如需禁用,请设置为 false。
// emitError: true, // 默认值:true
// 如果有任何错误,都将导致模块构建失败,如需禁用,请设置为 false。
// failOnError: true, // 默认值:true
// 如遇到警告将会被直接输出,如需禁用,请设置为 false。
// emitWarning: true, // 默认值:true
// 如果设置为 true,出现任何 warnings 都将会导致模块构建失败。
// failOnWarning: false, // 默认值:false
// 如果设置为 true,则仅处理和报告 errors,并忽略 warnings。
// quiet: false, // 默认值:false
// 将输出的错误写入文件,例如,用于上报的 json 文件。 其 filePath 会相对于 webpack 配置中的:output.path. 你可以为输出文件设置不同的 formatter,如果未设置,则将使用默认 formatter。
// outputReport: false, // 默认值:false
}),
]
}
{
plugins: [
// ...
// stylelint检测css
// https://stylelint.io/user-guide/options#allowemptyinput,参数就是stylelint.lint()的参数
new StylelintWebpackPlugin({
// 可指定为目录,文件名或 globs。目录会递归地寻找与 options.extensions 相匹配的文件。文件名和 glob 模式将忽略 options.extensions。
files: ['**/*.css', '**/*.less', '**/*.scss'], // 默认值:'**/*.(s(c|a)ss|css)'
// 设置为 true 时,会根据 cpu 的数量自动决定池子的大小。设置为大于 1 的数字时,可以确定池子的大小。设置为 false、1 或更小时,会禁用池子,并只在主线程运行。
threads: true, // 默认值:false
// 插件内部已经默认开启了缓存,可以不用设置
// cache: true, // 开启缓存
// cacheLocation: resolve(__dirname, './node_modules/.cache/stylelint-cache'), // 缓存保存目录
}),
]
}
格式化代码的工具
安装: npm install --save-dev --save-exact prettier
锁定prettier的版本
创建.prettierrc
文件,也不是必须创建,得看是使用什么方式进行校验代码的,比如本次设置的就是在webpack中配合stylelint一起使用
创建.prettierignore
文件,也不是必要的
# https://www.prettier.cn/docs/ignore.html
node_modules
dist
build
coverage
编辑器安装prettier插件,使用起来更方便
vscode安装prettier-vscode插件
下面是重点,上面几条除了安装以外,其他的都可有可无
本次是要配合stylelint一起校验,因此要下载插件stylelint-prettier插件
stylelint-prettier文档: https://github.com/prettier/stylelint-prettier
安装: npm install --save-dev stylelint-prettier
添加prettier的校验规则,任选一种就行
第一种,直接在stylelint.config.js中配置prettier的规则,在prettier/prettier中
// 在 sytlelint.config.js 中设置
{
"plugins": ["stylelint-prettier"],
"rules": {
'prettier/prettier':[
true,
/*
prettier设置 https://prettier.io/docs/en/options.html
*/
{
"tabWidth": 4, // 缩进 4
"useTabs": false, // false 缩进使用空格,true使用制表符
"singleQuote": false, // 使用单引号代替双引号
"semi": true, // 在语句末尾打印分号
"printWidth": 120, // 指定每行的最大列数
"endOfLine": "lf", // 设置行尾符号类型
}
]
}
}
既然是将prettier中的规则插入到stylelint中去校验,因此需要将vscode编辑器的校验更改为stylelint的校验
{
"[css]": {
"editor.defaultFormatter": "stylelint.vscode-stylelint"
},
}
第二种,在prettier的配置文件中设置,比如.prettierrc
中设置
首先就不用在stylelint.config.js
中设置prettier的规则了
// 在 sytlelint.config.js 中设置
{
"plugins": ["stylelint-prettier"],
"rules": {
'prettier/prettier': true
}
}
然后在.prettierrc
中设置prettier的规则
{
"overrides": [
{
"files": ["src/**/*.{css,less,scss}"],
"options": {
"tabWidth": 4, // 缩进 4
"useTabs": false, // false 缩进使用空格,true使用制表符
"singleQuote": false, // 使用单引号代替双引号
"semi": true, // 在语句末尾打印分号
"printWidth": 120, // 指定每行的最大列数
"endOfLine": "lf", // 设置行尾符号类型
}
}
]
}
这个没有将规则直接插入到stylelint中,因此需要更改编辑器的的校验程序为prettier
{
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
}
然后就是设置校验命令
-f verbose
会输出校验是否成功
--fix
会自动修复
// package.json中
{
"scripts": {
"lint": "stylelint -f verbose src/**/*.{css,less,scss}",
"lint:fix": "stylelint src/**/*.{css,less,scss} --fix"
},
}
推荐文章:
步骤:
安装husky: npm install husky -D
执行: npm set-script prepare "husky install"
就是在package.json中的scripts添加了一行
{
"scripts": {
"prepare": "husky install"
},
}
执行npm run prepare
这个命令做了什么事呢 ?实际上就是创建 .husky 目录,修改了.git/config文件里的hooksPath字段(设置 .husky 目录为 git hooks 目录)
执行: npx husky add .husky/pre-commit "npx lint-staged --allow-empty"
这个命令就是在.husky/pre-commit
中添加了文本npx lint-staged --allow-empty
,每次git commit都会执行pre-commit这个钩子中的内容
安装lint-staged: npm i lint-staged -D
package.json中设置
"lint-staged": {
"src/**/*.{css,less}": [
"stylelint src/**/*.{css,less}"
]
}
本插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载。
安装: install --save-dev mini-css-extract-plugin @types/mini-css-extract-plugin
类型: {String|Function}
默认: [name].css
此选项决定了输出的每个 CSS 文件的名称,只能决定入口chunk的文件名称
new MiniCssExtractPlugin({
// contenthash 只要文件内容没有改变,hash就不会改变
// 这样如果只有js文件改变了,css没有变化,那么contenthash就不会变,那么浏览器就会继续使用css的缓存文件
filename: 'css/[name][contenthash:5].css'
}),
https://juejin.cn/post/7026742035667222559#filename
类型: boolean
默认: false
好像是 来告诉它忽略 CSS 文件的顺序问题,没遇到过这个问题
只针对非入口chunk生成的文件
类型: chunkFilename?: string | ((pathData: PathData, assetInfo?: AssetInfo) => string)
默认: [模块id].css
决定非入口chunk文件的名称
比如有一个css文件是延迟加载进来的
import('./style/index.css');
这样生成的chunk
就不是从entry
生成的chunk
,这个chunkFilename
就有作用了
{
plugins: [
new MiniCssExtractPlugin({
// 此选项决定了输出的每个 CSS 文件的名称。根据entry中的入口名称
filename: 'css/[name].css',
// 决定非入口chunk文件的名称
chunkFilename: 'css/chunk_[contenthash:5].css'
})
]
}
就会在打包后的目录生成css/chunk_[contenthash:5].css
的文件,contenthash
由内容决定
只针对非入口chunk生成的文件
类型: insert?: string | ((linkTag: HTMLLinkElement) => void) | undefined;
默认: document.head.appendChild(linkTag)
只对非入口chunk文件的插入方式
import(/* webpackChunkName: "aCss" */ './style/a.css');
insert: '.title', // 会插入到 .title 类名元素的后边
// insert: function(link) {
// console.log(1111, link);
// link.setAttribute('nonce', '1234');
// document.head.append(link);
// }
只针对非入口chunk生成的文件
类型: attributes?: Record<string, string> | undefined;
默认: {}
非入口chunk文件的link标签上添加属性
import(/* webpackChunkName: "aCss" */ './style/a.css');
new MiniCssExtractPlugin({
// ...
// 非入口chunk文件的link标签上添加属性
attributes: {
nonce: '1234'
}
// ...
})
会在link标签上添加 nonce="1234"
的属性
好像还是针对非入口chunk文件的
类型: linkType?: string | false | undefined;
默认: text/css
设置link标签的type,好像还是针对非入口chunk文件的
类型: boolean
默认: true
允许开启/禁用 runtime 生成
不是明白,当设置成false时,异步加载的css就不会自动添加到html中了,需要手动引入执行,一般为true就行
当设置为false
时,异步加载的css文件,不能自动的加入到html中,需要借助assets-webpack-plugin
插件拿到所有的chunk路径
// index.js
import './style/index.css';
// import(/* webpackChunkName: "indexCss" */ './style/index.css');
import(/* webpackChunkName: "aCss" */ './style/a.css'); // 异步加载不能自动插入到html中
// webpack.config.js
import AssetsWebpackPlugin from 'assets-webpack-plugin';
// ...
plugins: [
new MiniCssExtractPlugin({
// ...
// 允许开启/禁用 runtime 生成
// 默认为 true
// 不是明白,当设置成false时,异步加载的css就不会自动添加到html中了,需要手动引入执行,一般为true就行
runtime: true
}),
new AssetsWebpackPlugin({
filename: 'assets.json', // 输出的文件名
fullPath: false, // 是否显示完整路径
removeFullPathAutoPrefix: true // 去除路径的前缀
})
]
会生成一个assets.json
文件
{
"index": {
"js": "js/index.js",
"css": "css/index.e5640.css"
},
"aCss": {
"css": "css/aCss.deddc.css",
"js": "js/aCss.593a4.js"
},
"barJs": {
"js": "js/barJs.29fdc.js"
},
"": {
"html": "index.html",
"js": "js/967.80fcc.js"
}
}
然后再需要插入css的js文件中插入css,先引入json文件,在找到需要插入的css文件路径,然后dom操作
// index.js
import('../assets.json').then(res => {
console.log(res);
const link = document.createElement('link');
link.href = res.aCss.css;
link.rel = 'stylesheet';
link.type = 'text/css';
document.head.append(link);
})
类型: boolean
默认: undefined
不懂
`>= 5.52.0` 版本的 webpack 不需要该配置,因为该配置项默认启用
仅 `>= 5.33.2 & <= 5.52.0` 版本的 webpack 需要该配置
对于 `<= 5.33.2` 版本的 webpack 不可用/不安全
new MiniCssExtractPlugin({
// 此选项决定了输出的每个 CSS 文件的名称。根据entry中的入口名称
filename: 'css/[name].[contenthash:5].css',
// 决定非入口chunk文件的名称
chunkFilename: 'css/[name].[contenthash:5].css',
})
类型: {String|Function}
默认: webpackOptions.output.publicPath
为图片、文件等外部资源指定一个自定义的公共路径。
background: url('../../../../static/mouse_cat_1.png');
/* 打包后:*/
background: url('aa/bb/cc8ea85193da95e617826a.png');
类型: Boolean
默认: true
如果设为 false,插件将会提取 CSS 但不会 生成文件
类型: Boolean
默认: true
将会生成使用 ES 模块语法的 JS 模块,设为false启用commonjs语法
rules: [
{
test: /.css$/,
exclude: /node_modules/,
use: [
// 生产环境下使用 mini-css-extract-plugin,开发环境下使用 style-loader
isProduction ? MiniCssExtractPlugin.loader : styleLoader,
{
loader: 'css-loader',
options: {
sourceMap: true
}
}
]
}
]
将css文件压缩优化
安装: npm i -D css-minimizer-webpack-plugin
文档上的写法
import CssMinimizerWebpackPlugin from 'css-minimizer-webpack-plugin';
// ...
optimization: {
minimizer: [
// 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`),将下一行取消注释
`...`,
// 生产模式下 压缩css
// TODO 很奇怪,在文档上看是要写在这儿的,但是开发模式下即使minimize: true,也不生效,写在plugin中就没有这个问题
// new CssMinimizerWebpackPlugin(),
],
// !坑,如果需要开发模式下也压缩,在设置这个属性,否则不要乱设置,可能会造成压缩失效
minimize: true, // true 开发模式下也压缩
}
发现上面的写法只会对生产模式下的css压缩,下面的写法就可以在生产和开发模式下都压缩了
plugins: [
// ...
// 压缩css
new CssMinimizerWebpackPlugin(),
],
optimization: {
// ...
// !坑,如果需要开发模式下也压缩,在设置这个属性,否则不要乱设置,可能会造成压缩失效
minimize: true, // true 开发模式下也压缩
}
导入一些公共的样式文件,比如:variables / mixins / functions,避免在每个样式文件中手动的@import导入
初始化npm: npm init -y
修改npm镜像: 在根目录新建.npmrc
文件,文件内容为: registry = "https://registry.npmmirror.com"
创建.gitignore
文件,忽略git要提交的文件,添加内容
node_modules
dist
安装构建工具: npm i -D webpack webpack-cli
安装设置环境变量的包:npm i -D cross-env
package.json的依赖
"devDependencies": {
"cross-env": "^7.0.3",
"webpack": "^5.88.0",
"webpack-cli": "^5.1.4"
}
src/index.js
,内容为console.log('index.js~~~~~');
webpack.config.js
文件,简单配置一下const path = require('path');
const isDev = process.env.NODE_ENV === 'development';
console.log('当前环境是:', isDev ? 'development' : 'production');
/** @type {import('webpack').Configuration} */
module.exports = {
mode: isDev ? 'development' : 'production',
entry: './src/index.js',
output: {
filename: 'index.js',
path: path.resolve(__dirname, './dist'),
clean: true,
}
};
"scripts": {
"build": "cross-env NODE_ENV=production webpack --progress"
},
npm run build
,应该是会生成一个目录dist/index.js
,内容与src/index.js
的内容一致,这样项目就初始换成功了。既然是要处理css,得先有html,不然无法查看css是否处理成功了
安装处理html的插件: npm i -D html-webpack-plugin
创建文件src/index.html
,内容为
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<ul>
<li>钱难挣!!</li>
<li>屎难吃!!</li>
</ul>
</body>
</html>
webpack.config.js
中添加plugins
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const isDev = process.env.NODE_ENV === 'development';
console.log('当前环境是:', isDev ? 'development' : 'production');
module.exports = {
plugins: [
new HtmlWebpackPlugin(
/** @type {HtmlWebpackPlugin.Options} */{
title: isDev ? '开发' : '生产',
filename: 'index.html',
template: path.resolve(__dirname, './src/index.html'),
favicon: path.resolve(__dirname, './static/mouse_cat_1.png'),
}
)
]
}
npm run build
,会在dist文件中生成一个index.html文件,浏览器直接打开,查看是否正确安装包: npm i -D style-loader css-loader less-loader mini-css-extract-plugin
修改webpack.config.js
style-loader将css-loader处理后的css以style标签插入到html中
css-loader将css处理成commonjs,方便style-loader用js将css插入到html中
less-loader将less处理成css
mini-css-extract-plugins是将css抽离成单独的文件
// ...
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// ...
// ...
const isDev = process.env.NODE_ENV === 'development';
console.log('当前环境是:', isDev ? 'development' : 'production');
// 普通的 css-loader
const cssLoader = {
loader: 'css-loader',
options: {sourceMap: true}
}
// 模块化的 css-loader
const cssModuleLoader = {
loader: 'css-loader',
options: {
sourceMap: true,
modules: {
mode: 'local', // 所有的css文件,开启局部作用域
localIdentName: isDev ? '[path]_[name]_[local]_[hash:base64:5]' : '[hash:base64]',
exportLocalsConvention: 'camelCase'
}
}
}
module.exports = {
// ...
module: {
rules: [
// css文件
{
test: /\.css$/,
exclude: /node_modules/,
oneOf: [
// 带有global字样的css就不开启模块化,less同理
{
test: /\\.global\\.css$/,
use: [
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
cssLoader,
]
},
{
use: [
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
cssModuleLoader,
]
}
]
},
// less文件
{
test: /\.less$/,
exclude: /node_modules/,
oneOf: [
{
test: /\.global\.less/,
use: [
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
cssLoader,
{loader: 'less-loader', options: {sourceMap: true}},
]
},
{
use: [
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
cssModuleLoader,
{loader: 'less-loader', options: {sourceMap: true}},
]
}
]
}
]
},
plugins: [
// ...
// 抽离css
new MiniCssExtractPlugin(
/** @type {MiniCssExtractPlugin.PluginOptions} */{
// 此选项决定了输出的每个 CSS 文件的名称。根据entry中的入口名称
filename: 'css/[name].[contenthash:5].css',
// 决定非入口chunk文件的名称
chunkFilename: 'css/[name].[contenthash:5].css'
}
)
]
}
src/css/index.global.css
src/css/index.css
src/less/index.global.less
src/less/index.css
随便写点样式看看生不生效就行/* index.global.css */
* {
box-sizing: border-box;
}
.flex {
display: flex;
}
/* index.css */
.list {
border: 2px solid black;
list-style: none;
}
/* index.global.less */
.p-20 {
padding: 20px;
}
.w-50 {
width: 50%;
}
/* index.less */
.ul-list {
min-height: 100px;
align-items: center;
:global {
.li-item {
color: red;
}
}
}
样式文件,引入入口文件index.js
并使用模块化类名,引入才会被打包
// index.js
import './css/index.global.css';
import cssStyle from './css/index.css';
import './less/index.global.less';
import lessStyle from './less/index.less';
{
console.log('cssStyle', cssStyle);
console.log('lessStyle', lessStyle);
const list = document.querySelector('.list');
list.classList.add(cssStyle.list);
list.classList.add(lessStyle['ul-list']);
}
console.log('index.js~~~~~');
html添加类名,css才会起作用
<body>
<ul class="list flex justify-center color-red w-50 p-20">
<li class="mr-18 li-item">钱难挣!!</li>
<li class="mr-18 li-item">屎难吃!!</li>
</ul>
</body>
修改package.json
"scripts": {
"build": "cross-env NODE_ENV=production webpack --progress",
"build:dev": "cross-env NODE_ENV=development webpack --progress"
},
npm run build
或者npm run build:dev
各自查看,处理后的样式是否生效,模块化下的类名是否有区别优化样式就是将css压缩、自动添加前缀、使用css新特性等,需要借助postcss
的功能
安装: npm i -D postcss-loader postcss
修改webpack配置,在css-loader之后使用postcss-loader,如果css-loader使用了模块化,还需要配置importLoaders
// webpack.config.js
const cssModuleLoader = {
loader: 'css-loader',
options: {
sourceMap: true,
modules: {
mode: 'local',
localIdentName: isDev ? '[path]_[name]_[local]_[hash:base64:5]' : '[hash:base64]',
exportLocalsConvention: 'camelCase'
},
// 加上这个属性
importLoaders: 2, // 模块化才需要配置,在css-loader之前,允许有几个loader
}
}
const postcssLoader = {
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
]
}
}
}
module.exports = {
module: {
rules: [
{
test: /\.css$/,
exclude: /node_modules/,
oneOf: [
{
test: /\\.global\\.css$/,
use: [
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
cssLoader,
postcssLoader
]
},
{
use: [
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
cssModuleLoader,
postcssLoader
]
}
]
},
{
test: /\.less$/,
exclude: /node_modules/,
oneOf: [
{
test: /\.global\.less/,
use: [
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
cssLoader,
postcssLoader,
{loader: 'less-loader', options: {sourceMap: true}},
]
},
{
use: [
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
cssModuleLoader,
postcssLoader,
{loader: 'less-loader', options: {sourceMap: true}},
]
}
]
}
]
}
}
// package.json
{
// ...
"browserslist": [
"> 0.1%",
"not dead"
]
}
使用css的新特性: npm i -D postcss-preset-env
,其内已经包含了autoprefixer
的功能
修改上面的第二部中的postcssLoader变量
// webpack.config.js
const postcssLoader = {
loader: 'postcss-loader',
options: {
sourceMap: true,
postcssOptions: {
plugins: [
'postcss-preset-env', // 已经包含了autoprefixer
// 'cssnano' // 在这儿使用cssnano,也可以使用css-minimizer-webpack-plugin,更偏向css-minimizer-webpack-plugin,因为他跟webpack结合的更好
]
}
}
}
压缩css: npm i -D css-minimizer-webpack-plugin
,内部默认也是使用的cssnano压缩的,但是跟webpack结合的更好
// webpack.config.js
const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
// ...
optimization: {
minimizer: [
// `...`,
// 压缩css
new CssMinimizerWebpackPlugin({
// parallel: true, // 默认就是开启的 多进程并发执行,os.cpus().length - 1
})
]
}
}
借助stylelint,在webpack中借助stylelint-webpack-plugin插件
npm i -D stylelint stylelint-webpack-plugin
安装vscode插件: vscode-stylelint
,用于在编辑器上直接显示css语法错误,并且设置vscode配置文件
在项目中创建.vscode/settings.json
,设置css校验的插件
{
"[css]": {
"editor.defaultFormatter": "stylelint.vscode-stylelint"
},
}
安装stylelint推荐的配置: npm i -D stylelint-config-standard
,已经配置好了许多规则
根目录创建文件stylelint.config.js
// stylelint.config.js
/** @type {import('stylelint').Config} */
module.exports = {
extends: ['stylelint-config-standard'],
overrides: [
{
files: 'src/**/*.css',
}
]
}
配置webpack.config.js
// webpack.config.js
const StylelintWebpackPlugin = require('stylelint-webpack-plugin');
module.exports = {
rules: [
new StylelintWebpackPlugin({
files: ['src/**/*.css'],
threads: true, // 开启多线程
// 插件内部已经默认开启了缓存,可以不用设置
// cache: true,
// cacheLocation: 'node_modules/.cache/stylelint-webpack-plugin/.stylelintcache',
})
]
}
修改css,然后npm run build
,查看是否会校验错误,并终止编译
.list {
border: 2px solid black;
list-style: none;
}
校验less文件也是类似的,修改如下文件,并且安装npm i -D postcss-less
stylelint.config.js
/** @type {import('stylelint').Config} */
module.exports = {
extends: ['stylelint-config-standard'],
overrides: [
{
files: 'src/**/*.css',
},
{
files: 'src/**/*.less',
customSyntax: 'postcss-less'
}
],
rules: {
// 不允许未知的伪类选择器
'selector-pseudo-class-no-unknown': [true, {
ignorePseudoClasses: ['global']
}]
}
}
webpack.config.js中的StylelintWebpackPlugin插件
// webpack.config.js
new StylelintWebpackPlugin({
files: ['src/**/*.{css,less}'],
threads: true, // 开启多线程
// 插件内部已经默认开启了缓存,可以不用设置
// cache: true,
// cacheLocation: 'node_modules/.cache/stylelint-webpack-plugin/.stylelintcache',
})
安装vscode插件: prettier-vscode
prettier配合stylelint一起校验,安装: npm i -D stylelint-prettier
配置stylelint.config.js,添加plugins和rules
{
plugins: ['stylelint-prettier'],
rules: {
'prettier/prettier': [true, {
'tabWidth': 4, // 缩进 4
'useTabs': false, // false 缩进使用空格,true使用制表符
'singleQuote': false, // 使用单引号代替双引号
'semi': true, // 在语句末尾打印分号
'printWidth': 120, // 指定每行的最大列数
'endOfLine': 'lf', // 设置行尾符号类型
}]
}
}
npm run build
,检查样式是否有错误
详见上面的git提交校验
asset/resource
发送一个单独的文件并导出 URL。之前通过使用 file-loader
实现。
asset/inline
导出一个资源的 data URI。之前通过使用 url-loader
实现。
asset/source
导出资源的源代码。之前通过使用 raw-loader
实现。
asset
在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader
,并且配置资源体积限制实现。
除了第三步外,其他步骤都是在配置项目,直接看第三步也可以
初始化项目,就不多说了
npm init -y
设置.npmrc
,更改镜像
设置 .gitignore
忽略一些不用提交的文件,比如node_modules、dist、.DS_Store等文件
安装npm i -D webpack webpack-cli
创建入口文件src/index.js
创建并配置 webpack.config.js
,简单配置下即可,先让项目可以跑起来
const webpack = require('webpack');
const path = require('path');
/** @type {webpack.Configuration} */
module.exports = {
entry: './src/index.js',
output: {
filename: 'index.js',
path: path.resolve(__dirname, 'dist'),
clean: true
}
}
修改package.json
,方便命令启动,然后启动命令,查看打包是否成功
处理一些资源比如图片、字体等,需要在浏览器上看效果,因此还需要处理并打包生成html文件
安装: npm i -D html-webpack-plugin
创建文件src/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<img src="./static/mouse_cat_1.png" alt="猫猫和鼠鼠鸭">
</body>
</html>
修改webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
templateParameters: {
title: isDev ? '开发' : '生产',
},
filename: 'index.html',
})
]
}
打包后发现图片显示的路径不正确,需要使用html-loader
来处理html中的图片资源路径,再次打包,就好了
现在打包图片,会发现,图片被处理到dist
目录下
使用webpack5中内置的asset module模块,处理图片资源,配置webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|jpeg|svg|gif)$/,
/*
文档: https://www.webpackjs.com/guides/asset-modules/
asset/resource: 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。
asset/inline: 导出一个资源的 data URI。之前通过使用 url-loader 实现。
asset: 自动地在 resource 和 inline 之间进行选择:小于 8kb 的文件,将会视为 inline 模块类型,否则会被视为 resource 模块类型。
asset/source: 导出资源的源代码。
*/
type: 'asset',
generator: {
// 打包后 文件路径前拼接的前缀,适合用来拼接 网络地址
// publicPath: 'https://cdn/assets/',
// 指定打包后,文件资源存放的文件夹'
// outputPath: 'assets',
// 文件名中 加上文件路径,也可以设置文件保存的位置,适合 本地的相对路径
filename: 'img/[name].[hash:5][ext][query]',
},
parser: {
/*
优点:减少请求次数
缺点:增大文件体积
因此 才判断 小文件,处理成base64,大文件,还是请求吧
*/
// 处理文件时,文件小于多少,自动处理成 base64 字符串
dataUrlCondition: {
maxSize: 200 * 1024 // 默认8kb
}
// 函数写法
// dataUrlCondition(source, { filename, module }) {
// // console.log(1111, source.toString());
// // console.log(2222, filename);
// const ext = extname(filename)
// var buff = fs.readFileSync(module.resourceResolveData.path);
// // console.log(2222, source.toString().length, Buffer.byteLength(source), Buffer.byteLength(buff));
// if (ext === '.webp' && Buffer.byteLength(source) > 32 * 1024) {
// return true
// }
// return false;
// }
}
},
// 字体图标 以及其他的资源,原封不动的 输出
{
test: /\\.(eot|ttf|woff2?|pdf|mp3|mp4|docx|txt)$/,
// 原封不动的 插入中 不转成 base64
type: 'asset/resource',
generator: {
filename: 'file/[name].[hash:5][ext][query]',
}
},
]
},
}
打包后,图片就会被保存到dist目录下的img文件夹下
补充:
可以在 generator.filename
中配置打包后输出的文件名,也可以在 output.assetModuleFilename
统一配置其他资源打包后输出的文件命名
{
output: {
filename: 'js/index.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
assetModuleFilename: 'asset/[name].[hash:5][ext][query]'
},
}
js中的图片和css中的图片都可以正常的处理
// index.js
import imgSrc from './static/mouse_cat_1.png';
console.log('hello world');
{
const html = `<div><img src="${imgSrc}" alt="" /></div>`;
document.body.innerHTML += html;
}
默认已经配置好了可以处理css文件,需要style-loader、css-loader)
/* index.css */
* {
box-sizing: border-box;
}
img {
font-size: 0;
vertical-align: top;
padding: 10px;
}
.bg {
width: 100px;
height: 100px;
padding: 10px;
background: url("../static/mouse_cat_1.png") no-repeat 0 0/contain;
background-clip: content-box;
}
初始化项目,应该已经很熟练了
初始化npm:npm init -y
创建 .npmrc
修改内容:registry="https://registry.npmmirror.com"
创建 .gitignore
添加git需要忽略掉的文件
创建 webpack.config.js
添加webpack配置项
安装 webpack、webpack-cli、cross-env: npm i -D webpack webpack-cli cross-env html-webpack-plugin
修改package.json
"scripts": {
"build:dev": "rm -rf dist && cross-env NODE_ENV=development webpack --progress",
"build": "rm -rf dist && cross-env NODE_ENV=production webpack --progress"
},
修改webpack.config.js
const webpack = require('webpack');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const isProd = process.env.NODE_ENV === 'production';
/** @type {webpack.Configuration} */
module.exports = {
mode: isProd ? 'production' : 'development', // 模式
entry: './src/index.js', // 入口文件
output: {
filename: 'js/index.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
templateParameters: {
title: isProd ? '生产' : '开发'
}
})
]
}
npm run build,生成的html文件,打开查看是否正确
webpack本身就可以直接处理 js和json文件,但是写代码的时候,并不能毫无忌惮的使用最新的语法,因为他可能在有些平台上不兼容,因此就需要babel来帮助你,将最新的语法,转换成可以在比较旧版本的平台上运行的代码。
比如在index.js中有一段代码
// index.js
const add = (x = 1, y = 2) => x + y;
const res = add();
console.log(res);
打包后dist/js/index.js目录
!function(){const o=((o=1,n=2)=>o+n)();console.log(o)}();
可以看到,几乎是原封不动的输出,这样如果需要在低版本的平台运行的话,可能有的平台就不支持形参的默认值,这时,就需要用到babel了。
babel的详细笔记可以看另一篇文章笔记
安装: npm i -D babel-loader @babel/core @babel/preset-env
既然是要兼容低版本的平台,那肯定是要配置 browserslist
的,在哪配置都行 package.json
或者 .browserslistrc
或者 babel.config.js
或者 webpack.config.js
// package.json
{
"browserslist": [
"> 0.02%",
"not dead"
]
}
配置webpack.config.js
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.m?js$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
// 只转换 语法,比如箭头函数、... 扩展,不会转换新增的api 比如 Object.assign等等
presets: ['@babel/preset-env']
}
}
]
}
]
},
}
然后再打包,发现函数形参被转换成了arguments来兼容低版本的浏览器
// 浏览器上复制的
!function() {
var n = function() {
return (arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : 1) + (arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : 2)
}();
console.log(n)
}();
上面通过@babel/preset-env
预设,将一些比较新的语法转换成低版本平台也可以识别运行的代码,但是并不能转换一些新增的api,比如Object.assign、Reflect、Promise等,这就需要借助一些polyfill,来帮助实现这些功能。
辅助函数的作用就是 @babel/preset-env
在转换语法的时候,需要用到一些函数帮助转换语法,而正常情况下,他会在每一个文件中都注入这些辅助函数,但是如果两个文件中都使用了相同的语法,这样就会在两个文件中都重复的注入这些辅助函数,因此要想办法,将辅助函数改为从外界引入的方式,所有文件都改为引入这些辅助函数,这样可以大大减少代码量。
需要借助到 @babel/runtime
和 @babel/plugin-transform-runtime
插件
@babel/runtime
中包含了 转换语法时,用到的辅助函数,安装 @babel/preset-env
的时候就带有了,但是还是推荐再安装一下,改为为生产依赖。
@babel/plugin-transform-runtime
帮助将注入到每个文件的辅助函数改为从 @babel/runtime
引入的模式
现在更改 index.js 中的内容
// index.js
import './aa.js';
class Preson {
static name = '张三';
}
console.log(Preson.name);
// aa.js
class Animal {
static name = '鼠鼠鸭';
}
console.log(Animal.name);
安装: npm i @babel/runtime
npm i -D @babel/plugin-transform-runtime
配置 webpack.config.js
// webpack.config.js
{
loader: 'babel-loader',
options: {
presets: [
// 只转换 语法,比如箭头函数、... 扩展,不会转换新增的api 比如 Object.assign等等
['@babel/preset-env', {
// entry 时,会将会 core-js 中所有的 polyfill 引入,
// 并且需要在入口文件处手动的引入 corejs, import 'core-js';
// "useBuiltIns": "entry",
// usage 时,会自动的按需加载 polyfill,也不需要在入口文件处手动的引入 import 'core-js'
useBuiltIns: "usage",
corejs: '3.31.1'
}]
],
plugins: [
'@babel/plugin-transform-runtime'
]
}
}
在此打包,首先发现代码量少了,然后在浏览器上查看源码发现,确实没有重复的函数注入了,要问为什么在浏览器上看,因为webpack打包后的代码在编辑器上实在是不好看,除非使用@babel/cli打包的,看起来要比webpack看起来要清晰的多。
这两种方式不能混用,选其一即可
这种方法一般就是用在项目开发的时候,但是可能会有污染全局windows对象上的属性的风险。
因为要测试是否添加polyfill是否成功,要使用低版本的浏览器,不太好整,这里取巧一下,在html中,将Object.entries 函数设置为undefined,这样打包之后,浏览器就会报错,没有这个函数
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title %></title>
<script>
window.Object.entries = undefined;
</script>
</head>
// index.js
const obj = {name: '张三', age: 18};
console.log(Object.entries(obj));
接下来使用polyfill,先安装npm i core-js
,有全部引入和按需引入两种方法,选其中一种就行,一般就是按需引入了
core-js 可以使用版本2或3,分别是
npm install core-js@3 --save
# or
npm install core-js@2 --save
借助@babel/preset-env
中的 useBuiltIns 和 corejs 属性,可以完成polyfill的引入
useBuiltIns: 'entry'
时,会将会 core-js 中所有的 polyfill 引入,并且需要在入口文件处手动的引入 import 'core-js'
;useBuiltIns: 'usage'
时,会自动的按需加载 polyfill,不需要在入口文件处手动的引入 import 'core-js'
corejs: 'xx'
,需要配合useBuiltIns使用才生效,安装的core-js是哪个版本,就写哪个版本号全部引入
// webpack.config.js
modules.export = {
module: {
rules: [
{
test: /\.m?js$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', {
"useBuiltIns": "entry",
corejs: '3.31.1' // core-js 安装的版本
}]
]
}
}
]
}
]
},
}
// index.js 入口文件的顶部
import 'core-js'`;
按需引入,修改useBuiltIns参数即可,也不需要在index.js中引入core-js了
{
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', {
"useBuiltIns": "usage", // 按需
corejs: '3.31.1'
}]
]
}
}
然后打包,发现原本在html中将entries函数设置为undefined,导致在浏览器上运行报错的现象消失了,大概率就是core-js帮助我们,重新再Object上添加了entries函数。
这种方式一般用在第三方库中,避免污染全局属性和方法,也比较好用
借助了 @babel/plugin-transform-runtime
的能力,配合 @babel/runtime-corejs3
,实现 polyfill 的能力。
@babel/runtime-corejs3
插件已经包含了上面 @babel/runtime
和 core-js
的能力,因此需要先卸载掉 npm uni @babel/runtime core-js
,并且删掉@babel/preset-env
关于polyfill的配置。
安装: npm i @babel/runtime-corejs3
然后配置webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.m?js$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env'
],
plugins: [
['@babel/plugin-transform-runtime', {
corejs: 3 // 安装的 @babel/runtime-corejs3 版本,2 or 3
}]
]
}
}
]
}
]
},
}
打包即可
在项目中如下,如果是第三方库,推荐使用上面的方式2 polyfill 方法
// webpack.config.js
modules.export = {
module: {
rules: [
{
test: /\.m?js$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true, // 开启缓存
cacheCompression: false, // 关闭 压缩缓存
presets: [
['@babel/preset-env', {
useBuiltIns: 'usage',
corejs: '3.31.1'
}]
],
plugins: [
'@babel/plugin-transform-runtime'
]
}
},
]
}
]
},
}
检查js语法
在webpack中需要用到 eslint-webpack-plugin
插件
安装: npm i -D eslint-webpack-plugin eslint
优先顺序
.eslintrc.js
.eslintrc.cjs
.eslintrc.yaml
.eslintrc.yml
.eslintrc.json
package.json
配置如下
也可以 npx eslint --init
,生成配置文件
// 安装 npm i -D eslint-define-config 可以有语法提示
const {defineConfig} = require('eslint-define-config');
module.exports = defineConfig({
root: true,
// 继承一些预设的规则
extends: ['eslint:recommended'],
// 指定环境
// https://eslint.nodejs.cn/docs/latest/use/configure/language-options#%E6%8C%87%E5%AE%9A%E7%8E%AF%E5%A2%83
env: {
es6: true, // es6中的全局变量
browser: true, // 浏览器全局变量
},
// 指定全局变量
// https://eslint.nodejs.cn/docs/latest/use/configure/language-options#%E6%8C%87%E5%AE%9A%E5%85%A8%E5%B1%80%E5%8F%98%E9%87%8F
globals: {
qxy: 'writable', // 允许覆盖
qqq: 'readonly', // 不允许覆盖
},
// 指定解析器选项
// https://eslint.nodejs.cn/docs/latest/use/configure/language-options#%E6%8C%87%E5%AE%9A%E8%A7%A3%E6%9E%90%E5%99%A8%E9%80%89%E9%A1%B9
parserOptions: {
ecmaVersion: 'latest', // 最新的js版本
sourceType: 'module', // 是否是 模块化
ecmaFeatures: {
'jsx': true
}
},
// 扩展 ESLint
plugins: [],
// 定制一些规则
// https://eslint.nodejs.cn/docs/latest/rules/
rules: {
'no-console': ['warn', {
allow: ['error', 'warn', 'dir'],
}],
// 不允许有空块
'no-empty': 'warn',
// 缩进格式
'indent': ['error', 4],
// 字符串的引号格式
'quotes': ['error', 'single'],
// 是否使用分号
'semi': ['error', 'always'],
// 不允许使用var
'no-var': 'error',
// 函数左括号 是否有空格
'space-before-function-paren': ['error', {
anonymous: 'never', // 匿名函数
named: 'never', // 具名函数
asyncArrow: 'always' // async 函数
}],
'no-multiple-empty-lines': ['error', {
max: 2, // 最大空行
maxBOF: 0, // 文件开头最大空行
maxEOF: 1, // 文件末尾最大空行
}],
// 不允许多个空格
'no-multi-spaces': 'error',
// 不允许在行尾尾随空格
'no-trailing-spaces': ['error', {
skipBlankLines: false,
ignoreComments: true, // 允许在注释块中使用尾随空格
}],
// 在分号前后强制执行一致的间距
'semi-spacing': ['error', {
before: false,
after: true,
}]
},
// 忽略的文件 或者在 .eslintignore 中配置
ignorePatterns: ['node_modules', 'dist'],
// 覆盖
// https://eslint.nodejs.cn/docs/latest/use/configure/configuration-files#%E8%A6%86%E7%9B%96%E5%A6%82%E4%BD%95%E5%B7%A5%E4%BD%9C
overrides: [
{
// 只能写在 这儿,写在外面校验就失效了
files: [
'.eslintrc.{js,cjs}',
'webpack.config.js'
],
env: {
node: true // Node.js 全局变量和 Node.js 作用域。
}
}
],
});
vscode上安装eslint插件,并且在项目根目录创建 .vscode/settings.json
,保存自动格式化
{
// 推荐笔记:https://juejin.cn/post/6844904068746313736
// "eslint.alwaysShowStatus": true 已废弃
// "eslint.autoFixOnSave": false, // 已废弃,被下面替代
// 保存时 格式化代码
"editor.codeActionsOnSave": {
"source.fixAll": false,
"source.fixAll.eslint": true // 开启 eslint 自动格式化
},
// 控制是否使用 eslint
"eslint.enable": true,
// 不显示eslint校验的警告信息
"eslint.quiet": false
}
在 webpack.config.js
中配置 eslint-webpack-plugin
,在编译的时候,可以校验错误
const EslintWebpackPlugin = require('eslint-webpack-plugin');
module.exports = {
plugins: [
new EslintWebpackPlugin({
context: path.resolve(__dirname, './src'),
files: ['**/*.{js,ts,jsx,tsx}'], // 指定要校验的目录、文件或 globs ,必须是相对于 options.context 的相对路径
// cache: true, // 默认就是 true
// cacheLocation: 'node_modules/.cache/eslint-webpack-plugin/.eslintcache', // 默认就是这个值
// TODO 开启多线程 导致eslint校验失效,
// issues回答 还没有解决: https://github.com/webpack-contrib/eslint-webpack-plugin/issues/146
// threads: true, // 开启多线程 默认false
})
]
}
修改js文件,打包,查看是否可以校验错误
在这不多描述,可以看css中的设置git提交检验
按照步骤来,完美运行,需要将stylelint的命令校验,改为eslint的就行。
使用 thread-loader
开启多进程,适用于项目构建开销比较大的时候
文档:https://webpack.js.org/loaders/thread-loader/
{
test: /\.m?js$/,
exclude: /node_modules/,
use: [
{
// 文档: https://webpack.js.org/loaders/thread-loader/
// 适用于 项目构建开销比较大的情况
loader: 'thread-loader',
options: {
workers: require('os').cpus().length // 进程数量 默认为 cpu的核数 - 1
}
},
{
loader: 'babel-loader',
options: {
cacheDirectory: true, // 开启缓存
cacheCompression: false, // 关闭 压缩缓存
}
},
]
}
babel-loader
开启缓存(默认关闭),开启缓存压缩(默认开启)
文档:https://webpack.js.org/loaders/babel-loader/
{
loader: 'babel-loader',
options: {
cacheDirectory: !isProd, // 默认为false 不开启缓存,开发环境再开启
cacheCompression: true, // 默认为true 压缩缓存,转移的文件特别多的时候,会有好处
}
},
eslint-webpack-plugin
开启缓存(默认就是开启的)和多进程(默认false,有bug,开启校验就失效了)
文档:https://webpack.js.org/plugins/eslint-webpack-plugin/
plugins: [
new EslintWebpackPlugin({
context: path.resolve(__dirname, './src'),
// files: ['src/**/*.js'], // 需要校验的文件
// cache: true, // 默认就是 true
// cacheLocation: 'node_modules/.cache/eslint-webpack-plugin/.eslintcache', // 默认就是这个值
// TODO 开启多线程 导致eslint校验失效,
// issues回答 还没有解决: https://github.com/webpack-contrib/eslint-webpack-plugin/issues/146
// threads: true, // 开启多核 默认false
})
],
js压缩开启多进程(默认开启),但是是 cpu的核数-1 文档:https://webpack.js.org/plugins/terser-webpack-plugin/
optimization: {
minimizer: [
// eslint-disable-next-line
`...`,
new TerserWebpackPlugin({
parallel: require('os').cpus().length, // 开启多进程 默认并发运行数: os.cpus().length - 1 。
})
]
}
先初始化项目,不多说了,应该很熟练了。
npm init -y
, mkdir src
, touch webpack.config.js src/index.js index.html .npmrc .gitignore .editorconfig
在 .npmrc
中添加npm的镜像地址 registry="https://registry.npmmirror.com"
在 .gitignore
和 editorconfig
和 index.html
中分别添加
# .gitignore
node_modules
dist
.DS_Store
# editorconfig
[*]
charset = utf-8
insert_final_newline = true
end_of_line = lf
indent_style = space
indent_size = 4
max_line_length = 80
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title %></title>
</head>
<body></body>
</html>
安装包 npm i -D webpack webpack-cli html-webpack-plugin cross-env
,并初始化 webpack.config.js
配置
// webpack.config.js
const webpack = require('webpack');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const isDev = process.env.NODE_ENV === 'development';
/** @type {webpack.Configuration} */
module.exports = {
mode: isDev ? 'development' : 'production',
entry: {
index: './src/index.js'
},
output: {
filename: 'js/[name]_[contenthash:5].js',
path: path.resolve(__dirname, './dist'),
clean: true,
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, './index.html'),
templateParameters: {
title: isDev ? '开发' : '生产'
}
})
]
}
配置 package.json
"scripts": {
"build": "cross-env NODE_ENV=production webpack --progress",
"build:dev": "cross-env NODE_ENV=development webpack --progress"
},
执行命令 npm run build
查看打包结果是否正常
文档:https://www.webpackjs.com/configuration/optimization/#optimizationsplitchunks
作用:用来分割打包后的文件,将多次引用的文件抽离成单独的文件,将node_modules中引用的文件,抽离成单独的文件。
默认值如下:
基本不用怎么配置,因为默认对 node_modules 进行分割。其他的文件至少被引用2次,而且文件大小得大于20000字节的才会被分割。
只需要把 chunks 的值,改为all,就行,下面为了演示,会故意改一些选项,达到分割的目的。
// webpack.config.js
module.exports = {
// 代码分割
splitChunks: {
chunks: 'async', // 'all' 对所有模块进行分割
// 下面的是默认配置
minSize: 20000, // 要生成的块的最小大小(以字节为单位)
minRemainingSize: 0, // 类似 minSize, 最后确保提取的文件大小不能为0
minChunks: 1, // 至少被引用的次数,满足条件才会被分割
maxAsyncRequests: 30, // 按需加载时的最大并行请求数
maxInitialRequests: 30, // 入口点的最大并行请求数
enforceSizeThreshold: 50000, // 超过 50kb的 一定会被单独打包(此时会忽略 minRemainingSize maxAsyncRequests maxInitialRequests)
// 下面 选项中如果 没有设置的属性,会使用上面设置的属性
cacheGroups: { // 哪些模块要被打包到一组
// node_modules 引入的文件
defaultVendors: { // 组名
test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
priority: -10, // 权重(越大越高)
reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出去的模块,则它将被重用,而不是生成新模块
},
// 多入口文件,每个入口都引入相同的文件才可能生效
default: { // 其他没有写的配置会使用 上面的默认值
minChunks: 2, // 这里的 minChunks 权重更大
priority: -20,
reuseExistingChunk: true,
},
},
}
}
}
现在有三个文件:index.js
about.js
utils.js
// utils.js
export const add = (...rest) => {
return rest.reduce((total, item) => {
return total + item;
}, 0);
}
// index.js
const {add} = require('./utils');
console.log('===== index.js =====');
{
const x = 1;
const y = 2;
const res = add(1, 2);
console.log(res);
}
// about.js
const {add} = require('./utils');
console.log('----- about.js -----');
{
const x = 'x';
const y = 'y';
console.log(add(x, y));
}
修改 webpack.config.js
文件为多入口
entry: {
index: './src/index.js',
about: './src/about.js',
},
生产模式下,打包后,发现无论 index.js
还是 about.js
中,都被注入了add函数,这样就有些重复了,增大了代码量。
使用 splitChunks
,对多次引用的代码进行分割,单独抽离出来,其他模块共享抽离出来的代码,这样大大的减少了代码量。
设置 splitChunks
,一般来说不需要太精细的设置,直接设置个 chunks: 'all'
,就可以了。这里只是为了出效果。。。
// webpack.config.js
module.exports = {
optimization: {
splitChunks: {
// 多入口情况下,为了看效果,手动改一些配置
chunks: 'all',
cacheGroups: {
// 多入口文件的情况下才会生效,单入口的时候,没用
default: {
minSize: 100, // 大于100字节的文件才 分割
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
}
},
}
}
}
再次在生产环境下打包,发现 utils.js(117bytes)
被分割出来了,index.js
和about.js
,中不在有add函数的代码。
==补充==,在 runtimeChunk 文档中有提示,如果是在同一个页面中有多个入口文件,最好设置为 'single',实测如果在 index.js 文件中,再使用 utils.js 的其他函数(比如 ride),而在 about.js 中没有使用,会导致打包后 index.js 中的的 add 和 ride 函数还是被注入进 index.js 文件中的,并没有被被分割出去,但是加上 runtimeChunk: 'single'
之后,就恢复正常了。
文档地址: https://www.webpackjs.com/configuration/optimization/#optimizationruntimechunk
module.exports = {
// ...
optimization: {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
cacheGroups: {
default: {
minSize: 100, // 大于100字节的文件才 分割
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
}
}
}
}
}
其实只用设置个 chunks 即可,其他配置默认就可以,可以将 node_modules 给抽离出来
module.exports = {
// ...
optimization: {
splitChunks: {
chunks: 'all',
}
}
}
比如在项目中引入了lodash,打包后就会将lodasj单独提取成一个文件
// index.js
import {isInteger} from 'lodash';
import {ride, subtract} from './utils';
console.log('===== index.js =====');
{
const x = 1;
const y = 2;
console.log('ride', ride(x, y));
console.log('subtract', subtract(x, y));
console.log('isInteger', isInteger(12.999999999999999999999999999)); // 精度爆了
}
配置文档:https://www.webpackjs.com/configuration/optimization/#optimizationruntimechunk
相关文档:https://www.webpackjs.com/guides/code-splitting/#entry-dependencies
作用:webpack打包后,会在每个入口文件中插入 webpack 运行时代码,当有多个入口文件时,就显得重复了,因此可以使用这个配置将运行时代码抽离出来,可以减少代码体积。
运行时代码就是 Webpack 在打包过程中生成的一些辅助代码,负责模块的加载和执行。
使用:
// webpack.config.js
module.exports = {
// ...
optimization: {
/*
- 值为 single 时,会将 所有入口文件共享的运行时代码提取到一个单独的文件中。
- 值为 multiple 或 true 时,会将 每个入口文件都会有自己独立的运行时代码文件。
- 当值为一个函数时,可以设置生成的 runtime 文件的名称。
*/
runtimeChunk: 'single',
}
}
使用后,会发现打包后,多出来一个运行时的文件。
补充:
值为 true 和 multiple 时,适合多页面应用使用,每个页面都有自己的入口文件,都使用 自己的运行时代码;当然也可以多个页面都使用一个运行时文件。
如果是 单页面应用,但是有 多个入口文件,使用 true 或 multiple 时,可能会出现bug,比如每个文件执行两次。 所以单页面应用,推荐使用 single。
还有一种方式也可以抽离出来
// webpack.config.js
module.exports = {
entry: {
index: {
import: './src/index.js',
// 如果 两个入口文件的 runtime 值一样,则会生成一个共享的 runtime 文件,类似 single
// 如果不一样,则各自生成各自的 runtime 文件,类似 multiple
runtime: 'common-runtime', // 指定生成的 runtime 文件名的前缀
},
about: {
import: './src/about.js',
runtime: 'common-runtime',
},
}
}
在 webpack 中,使用 import 动态导入,就会单独的创建一个 chunk,在需要用到的时候,才会加载 文档:https://www.webpackjs.com/guides/code-splitting/#dynamic-imports
在 react 中的使用方式如下
import {Suspense, lazy} from 'react';
// 懒加载的组件
const Home = lazy(() => import(/* webpackChunkName: "home" */'./Home'));
const About = lazy(() => import(/* webpackChunkName: "about" */'./About'));
export default function App() {
return (
<>
<ul>
<li><Link to="/home">Home</Link></li>
<li><Link to="/about">About</Link></li>
</ul>
// 被 Suspense 组件包裹才可
<Suspense>
<Routes>
<Route path="/home" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</>
);
}
new StylelintWebpackPlugin({
files: ['src/**/*.{css,less}'],
threads: true, // 默认 false
cache: true, // 默认 true
cacheLocation: resolve(__dirname, './node_modules/.cache/stylelint-cache'), // 默认值
})
@babel/plugin-transform-runtime
插件将 @babel/runtime
辅助函数改为从 node_modules 引入的形式@babel/preset-env
开启按需引入 corejs,将 useBuiltIns: 'usage'
export const jsLoader: RuleSetRule = {
test: /\.[j|t]s$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true, // 开启缓存
cacheCompression: false, // 关闭 压缩缓存
presets: [
[
'@babel/preset-env', {
useBuiltIns: 'usage', // 按需引入 core-js
corejs: '3.32.0',
}
]
],
plugins: [
'@babel/plugin-transform-runtime', // 提取辅助函数
]
}
}
]
}
// 校验js
new EslintWebpackPlugin({
context: join(process.cwd(), '/src'),
files: ['**/*.{js,ts,jsx,tsx}'], // 指定要校验的目录、文件或 globs ,必须是相对于 options.context 的相对路径
cache: true, // 默认就是 true
cacheLocation: join(process.cwd(), '/node_modules/.cache/eslint-webpack-plugin/.eslintcache'), // 默认就是这个值
// TODO 开启多线程 导致eslint校验失效,
// issues回答 还没有解决: https://github.com/webpack-contrib/eslint-webpack-plugin/issues/146
threads: false, // 开启多线程 默认false
})
压缩 css:css-minimizer-webpack-plugin
压缩js: terser-webpack-plugin 默认就有
压缩图片:image-minimizer-webpack-plugin
output文档: https://www.webpackjs.com/configuration/output/
有好多配置,也有跟多配置搞不懂,慢慢来吧 😭
{
// output文档: https://www.webpackjs.com/configuration/output/
output: {
filename: 'js/[name]_[contenthash:5].js',
// 此选项决定了非初始(non-initial)chunk 文件的名称
chunkFilename: 'js/chunk_[name]_[contenthash:5].js',
// 与 output.filename 相同,不过应用于 Asset Modules。
assetModuleFilename: 'asset/[name].[id:5][ext][query]',
// 创建按需加载的异步 chunk。默认 true,为 false 时,import 动态加载就不会单独生成一个 chunk
asyncChunks: true,
path: path.resolve(__dirname, './dist'),
clean: true,
}
}
{
output: {
// 文档:https://www.webpackjs.com/configuration/output/#outputlibrary
// 打包后,以什么样的格式输出,比如 var amd umd commonjs2 等等
library: {
// 指定库的名称。其他模块可以直接使用这个名称来访问
// name: 'test_aa',
// 配置将库暴露的方式
type: 'umd',
// 给 umd 添加注释,感觉没啥用,生产环境下,注释都自动的删除了
// auxiliaryComment: {
// root: 'Root Comment',
// commonjs: 'CommonJS Comment',
// commonjs2: 'CommonJS2 Comment',
// amd: 'AMD Comment',
// }
},
}
}
需要现将 webpack.config.js
中的 library.type
改为 amd,library.name
也要设置
module.exports = {
output: {
library: {
// 指定库的名称。其他模块可以直接使用这个名称来访问
name: 'MyModule',
// 配置将库暴露的方式
type: 'amd',
},
}
}
在 index.html
中添加 requirejs
,不添加的话,打包后会报错,提示不存在 define
<head>
<meta charset="UTF-8">
<title><%= title %></title>
<script src="https://cdn.jsdelivr.net/npm/requirejs@2.3.5/require.min.js"></script>
</head>
<body></body>
<script>
// library.name 指定的名称,引用一下,才会执行打包后的代码
require(['MyModule'], function (index) {
console.log(index);
})
</script>
打包后试试
跟上面的那种方式比起来就简单很多,不需要在html中手动引入打包后的文件,也可以不用设置 library.name
library.type
改为 amd-require
module.exports = {
output: {
library: {
// 配置将库暴露的方式
type: 'amd-require',
},
}
}
在 index.html
中添加 requirejs
,不添加的话,打包后会报错
<head>
<meta charset="UTF-8">
<title><%= title %></title>
<script src="https://cdn.jsdelivr.net/npm/requirejs@2.3.5/require.min.js"></script>
</head>
打包后试试
用来设置模块该如何被解析
文档:https://www.webpackjs.com/configuration/resolve/
取别名
module.exports = {
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
}
},
}
省略后缀
module.exports = {
resolve: {
// '...' 表示 继承webpack默认的省略后缀配置
// 默认值为 ['.js', '.json', '.wasm'],
extensions: ['...', '.ts', '.tsx', '.jsx'],
},
}
模块解析失败时,重定向模块请求。
webpack 5 不再自动 polyfill Node.js 的核心模块,这意味着如果你在浏览器或类似的环境中运行的代码中使用它们
比如在 index.js 中使用 querystring 模块,它会提示你,如何去配置和安装
module.exports = {
resolve: {
fallback: {
querystring: require.resolve('querystring-es3') // npm i querystring-es3
}
},
}
感觉没啥用,如果加载一个模块失败,那直接去安装这个模块不就行了,为什么非要在 fallback 中配置一下呢,顶多改个别名。
允许在 编译时 将你代码中的变量替换为其他值或表达式。
// webpack.config.js
module.exports = {
plugins: [
// ...
new webpack.DefinePlugin({
TITLE: JSON.stringify('练习webpack'),
'typeof window': JSON.stringify('123'), // 使用 typeof window 就会变成 123
})
],
}
打包后
// index.js
console.log(TITLE); // 练习webpack
console.log(typeof window); // 123
自动加载模块,而不必到处加载 import
require
模块。
new ProvidePlugin({
_: 'lodash',
})
在 index.js 文件中,直接使用即可
const arr = [1, 2, 3, 4];
console.log(_.includes(arr, 3));
使用 DefinePlugin
on process.env
键的简写
文档:https://www.webpackjs.com/configuration/watch/
npm run build 后,可以监听文件是否发生改变,重新编译
{
watch: true,
watchOptions: {
aggregateTimeout: 1000, // 延迟1秒编译
ignored: /node_modules/, // 忽略的文件
// poll: 1000, // 自动刷新,也可以设置数字
}
}
就是 热更新,当有文件发生改变时,自动重新的编译生成最新的结果。
webpack-dev-server 一般是默认开启的
还需要在入口文件中配置,写法有很多,详见文档。
文档:https://www.webpackjs.com/guides/hot-module-replacement/
if (module.hot) {
module.hot.accept(function() {
console.log('Accepting the updated printMe module!');
});
}
但是一般都不这样写,各个框架有各个框架的插件。
react 更新插件
插件:https://www.npmjs.com/package/@pmmmwh/react-refresh-webpack-plugin
修改 webpack.config.js 的plugins 配置
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
export default {
plugins: [
new ReactRefreshWebpackPlugin()
]
}
修改 babel-loader 的 plugins 配置
{
loader: 'babel-loader',
options: {
// ...
plugins: [
isDev ? 'react-refresh/babel' : ''
]
}
}
排除依赖,打包时不打包进最后的源码中
文档:https://www.webpackjs.com/configuration/externals/
// webpack.config.js
module.exports = {
// 控制哪些包最后不会被打包进最终的dist目录中
// externalsType: 'umd', // 指定 externals 的类型
externals: { // 文档:https://www.webpackjs.com/configuration/externals/
lodash: '_'
},
// 加载cdn
// externalsType: 'script',
// externals: {
// 'lodash': ['https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min.js', '_'],
// }
}
文档:https://www.webpackjs.com/guides/code-splitting/#prefetchingpreloading-modules
const obj1 = {a: {xx: 'xxx'}};
const obj2 = {a: {yy: 'yyy'}};
merge(obj1, obj2) // a: {yy: 'yyy'}
// 发现 obj1 中的 xx 被 yy覆盖掉了
// 可以使用这个属性
const obj1 = {a: {xx: 'xxx'}};
const obj2 = {a: {yy: 'yyy'}};
merge.recursive(obj1, obj2) // a: {xx: 'xxx', yy: 'yyy'}
const obj1 = {a: {xx: 'xxx'}, arr: [{a: 11}, {b: 22}]};
const obj2 = {a: {yy: 'yyy'}, arr : [{c: 22}]};
merge.recursive(obj1, obj2)
// {
// "a": {
// "xx": "xxx",
// "yy": "yyy"
// },
// "arr": [
// {
// "c": 22
// }
// ]
// }
const obj1 = {a: {xx: 'xxx'}};
const obj2 = {a: {yy: 'yyy'}};
const obj3 = merge.recursive(true, obj1, obj2)
obj3 === obj1 // false
obj3 === obj2 // false
const obj1 = {a: {xx: 'xxx'}};
const obj2 = {a: {yy: 'yyy'}};
const obj3 = merge(true, obj1, obj2)
obj3 === obj1 // true
obj3 === obj2 // false
2023.7.28
版本
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1"
配置
import {Configuration, HotModuleReplacementPlugin} from 'webpack';
import 'webpack-dev-server';
import merge from 'merge';
import {baseConfig} from './webpack.base.config';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import {extname, resolve} from 'path';
import {readFileSync} from 'fs';
const PORT = 8888;
const {ENV} = process.env;
console.log(ENV);
const config: Configuration = {
mode: ENV === 'development' ? 'development' : 'production',
entry: {
index: resolve(__dirname, '../src/index.js'),
app: resolve(__dirname, '../src/app.js')
},
output: {
path: resolve(__dirname, '../dist'),
filename: 'js/[name].js',
clean: true,
},
module: {
rules: [
]
},
plugins: [
new HtmlWebpackPlugin({
title: '自定义html', // 如果设置了 template,将以template中的内容为准
filename: 'index.html',
template: resolve(__dirname, '../src/index.ejs'),
cache: true, // 默认为true
hash: true, // 将注入的 js css等文件名身上加上hash,有助于清除缓存
showErrors: true, // 默认为true,错误信息将写入html页面
chunks: ['index', 'app'], // 插入一些文件
templateParameters: { // 这个参数对象中的变量,可以直接在 template 模板中使用
title: 'custom html',
text: '我是h1标题',
},
inject: 'head',
favicon: resolve(__dirname, '../src/img/favicon.ico')
}),
],
// optimization: {
// runtimeChunk: 'multiple'
// },
devServer: {
compress: true,
host: 'localhost',
port: PORT,
hot: false,
}
}
export default merge.recursive(baseConfig, config);
当entry有多个入口文件时,只有一个入口文件的热更新生效,其他的入口文件会报错
目前解决的办法
第一种:webpack-dev-server 中的 hot: false
设置成false
第二种:设置optimization
optimization: {
runtimeChunk: 'multiple'
},
目前没有更好的解决方案
issues: https://github.com/webpack/webpack-dev-server/issues/2792
打包时,字体文件过大,该如何解决?
使用 字体子集?会导致一些字符格式不对,使用cdn?暂时没有那个能力。还没有一个比较好的方法。
单独使用 html-webpack-plugin 时,打包后一切正常,但是处理图片时,是不会自动处理图片路径的,会原封不动的输出到打包后的html中,导致图片加载不出来,因此想到了使用 html-loader ,会自动的帮忙处理一些路径。但是导致 ejs 模版语法失效,因为在没有使用 html-loader 时,html-webpack-plugin 默认使用的是内部基于 loader 模版的 ejs 语法,如果使用了其他加载器,就不会使用默认的了,因此类似这样的语法将会失效<title><%= title %></title>
,该如何解决呢。
推荐笔记:https://www.cnblogs.com/feng-gamer/p/14836084.html
但是我使用的是另一种方案,这样,在 html-loader 中,就可以处理ejs的语法了
文档:https://webpack.docschina.org/loaders/html-loader/#templating
ejs文档:https://ejs.bootcss.com/#docs
// html
import ejs from 'ejs';
export const htmlLoader: RuleSetRule = {
test: /\.html$/,
loader: 'html-loader',
options: {
preprocessor: (content, loaderContext) => {
let result;
try {
// 使用 ejs 处理 html
result = ejs.render(content, {title: 'webpack练习'}, {});
} catch (error) {
loaderContext.emitError(error);
return content;
}
return result;
},
esModule: false
}
}
笔记:https://juejin.cn/post/7112343439731064846
如果ts报错,没有引入React,也可能是 tsconfig.json 没有配置好
https://www.typescriptlang.org/tsconfig#jsx
{
"compilerOptions": {
"jsx": "react-jsx", // 试试这个
}
}
还有 @babel/preset-react 在 babel 8 才会默认导入 react 因此可能也需要配置
https://www.babeljs.cn/docs/babel-preset-react#both-runtimes
{
loader: 'babel-loader',
options: {
cacheDirectory: true, // 开启缓存
cacheCompression: false, // 关闭 压缩缓存
presets: [
//...
[
'@babel/preset-react', {
runtime: 'automatic' // 自动引入 React 不需要在手动引入
}
]
],
plugins: [
'@babel/plugin-transform-runtime',
]
}
}
eslint的话,也可能会提示,可能需要将这两个规则关闭
https://zh-hans.legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html#eslint
'react/jsx-uses-react': 'off',
'react/react-in-jsx-scope': 'off'
jsconfig.json
或者tsconfig.json
中配置
{
"compilerOptions": {
...
"baseUrl": "./",
"paths": {
"@/*": ["./src/*"],
},
},
"exclude": ["node_modules", "dist"],
"include": ["src/**/*"],
}
console.log('\x1b[31m', '这段文字是红色的', '\x1b[0m');
其中,\x1b[31m
表示设置输出颜色为红色,\x1b[0m
表示重置颜色。
此外,还有其他的 ANSI 转义码,可以实现更多不同样式的文本效果
const color = {
bright: '\x1B[1m', // 亮色
grey: '\x1B[2m', // 灰色
italic: '\x1B[3m', // 斜体
underline: '\x1B[4m', // 下划线
reverse: '\x1B[7m', // 反向
hidden: '\x1B[8m', // 隐藏
black: '\x1B[30m', // 黑色
red: '\x1B[31m', // 红色
green: '\x1B[32m', // 绿色
yellow: '\x1B[33m', // 黄色
blue: '\x1B[34m', // 蓝色
magenta: '\x1B[35m', // 品红
cyan: '\x1B[36m', // 青色
white: '\x1B[37m', // 白色
blackBG: '\x1B[40m', // 背景色为黑色
redBG: '\x1B[41m', // 背景色为红色
greenBG: '\x1B[42m', // 背景色为绿色
yellowBG: '\x1B[43m', // 背景色为黄色
blueBG: '\x1B[44m', // 背景色为蓝色
magentaBG: '\x1B[45m', // 背景色为品红
cyanBG: '\x1B[46m', // 背景色为青色
whiteBG: '\x1B[47m' // 背景色为白色
}