Vite学习笔记(三)如何兼容旧项目中的react-css-module并实现热更新

我们在迁移到vite的时候,可能会遇到一个情况,就是旧的项目中如果采用webpack,可能会将css文件直接当作css moudle去使用,但是这一点在vite中是不支持的,想要当作css module去引入,就必须以module.css作为扩展名,在研究了以下vite的源码之后,我们可以通过插件来实现这个无缝切换,并且实现css的热更新。

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import { transformSync } from '@babel/core';
const cssLangs = `\\.(css|less|sass|scss|styl|stylus|pcss|postcss)($|\\?)`;
const importRE =
/import\s+([\S]+)\s+from\s+('|")([\S]+)\.(css|less|sass|scss|styl|stylus|postcss)(\?[\S]*)?('|")/;
const jsRE = /\.(js|mjs|ts|jsx|tsx)/;
const cssLangRE = new RegExp(cssLangs);
const cssModuleRE = new RegExp(`\\.module${cssLangs}`);

function autoCSSModulePlugin() {
return () => {
return {
visitor: {
ImportDeclaration: (path) => {
const { node } = path;
if (!node) {
return;
}
// 如果不是module css的那么就通过 转化为 module.styl来模块化css
if (
node.specifiers &&
node.specifiers.length > 0 &&
cssLangRE.test(node.source.value) &&
!cssModuleRE.test(node.source.value)
) {
const cssFile = node.source.value;
// node.source.value = cssFile + (cssFile.indexOf('?') > -1 ? '&' : '?') + '.module.styl';
node.source.value = cssFile.replace('.css', '.css?__CSS_TO_MODULE_FLAG__=1&.module.styl');
}
},
},
};
};
}

function transform(code, { sourceMap, file }) {
// const parsePlugins = ['jsx'];
// if (/\.tsx?$/.test(file)) {
// parsePlugins.push('typescript');
// }

const result = transformSync(code, {
configFile: false,
parserOpts: {
sourceType: 'module',
allowAwaitOutsideFunction: true,
plugins: ['jsx', 'typescript'],
},
sourceMaps: true,
sourceFileName: file,
inputSourceMap: sourceMap,
plugins: [autoCSSModulePlugin()],
});

return {
code: result.code,
map: result.map,
};
}

export default function viteCSSToModulesPlugin() {
const name = 'vite-plugin-css-to-modules';
return {
name,
confirm() {
return {
css: {
modules: {
scopeBehaviour: 'local',
localsConvention: 'dashes',
},
},
}
},
transform(code, id) {
if (jsRE.test(id) && importRE.test(code)) {
const result = transform(code, {
file: id,
sourceMap: this.getCombinedSourcemap(),
});
if (result) {
return result;
}
}
return undefined;
},
};
}

原理分析

这个代码主要就是做了一件事,在分析文件中是否有import css的代码,如果有的话,将形如 import styles from a.css 变为 import styles from a.css?__CSS_TO_MODULE_FLAG__=1&.module.styl

其实只要在问号后面的参数中有.module.styl就可以让vite把该css当作module处理了,但是这样会导致每次css更新都会让页面full-reload,经过源码分析,是因为更新之后,vite会在引入链接中拼接时间戳。

即如果我们只是将链接转化成import styles from a.css?.module.styl,在vite进行入口分析时,它的id为a.css?.module.style那么在第一次更新之后,vite引入的url会变成import styles from a.css?t=1234567890000&.module.styl,这个链接在vite内部分析依赖的时候,他的id会被处理成,a.css?&.module.styl,多了一个&符号,会被识别成另一个文件,而vite在此时会让页面整个full-reload。而在前面随便加入另一个参数,即可让每次识别出来的id都是a.css?__CSS_TO_MODULE_FLAG__=1&.module.styl,也就是说,__CSS_TO_MODULE_FLAG__=1你可以换成任何键值对。