在实际开发过程中,可能会遇到在 Electron 项目中需要引用组件库的情况。因为组件库往往除了 JavaScript 文件之外,还连带有 CSS 文件,因此一般即便有 lib
文件的输出(也就是 JavaScript 经过了预编译,在 Node.js 环境下可以直接运行),也没法直接使用。这里 CSS 文件一般通过 import 'styles.css';
这样的语法引入,编译后会变成 require('styles.css')
。由于 Node.js 不支持直接 require
CSS 文件,因此 lib
在 Electron 下是没法直接运行的。
针对这种情况,常规的做法是通过 Webpack 将组件库打包到最终的产物中,通过 css-loader 和 mini-css-extract-plugin 消化 CSS 产物,最终在 Electron / Web 环境下运行。
然而,这样的做法可能面临几个问题:
- 如果 Electron 的不同 Webview 需要组件库的不同部分(不完全重叠),那么实践上最方便的做法只能是分别打包。相当于同一份代码被复制了多份,存在于各自的打包产物中,造成了包体积的浪费;
- 上面的做法其实是 Web 的,Electron 环境的优势(包含 Node.js)并没有体现出来。
因为 Node.js 在进行文件载入(require
)的时候,提供了扩展能力。因此,只需要做如下的代码增强,在 Electron 项目中也可以直接 require
CSS 文件了:
const fs = require('fs');
// 当 Node.js 需要 require 任意后缀是 .css 的文件时,就会执行这个自定义的回调
require.extensions['.css'] = function (module, filename) {
const css = fs.readFileSync(filename, 'utf8');
/**
* 这里让 Electron 支持 require CSS 的思路非常简单,类似于 Webpack 中的 style-loader:
* 首先创建一个 <style> 标签;
* 然后将 CSS 文件的内容读取出来;
* 将 CSS 的内容插入到 <style> 中并最终插入到 <head> 里面;
* 剩下的渲染工作就交给浏览器了。
*/
const js = [
`const css = ${JSON.stringify(css)};`,
/**
* 将引用文件的路径作为 id
* 用于确保同一个 CSS 文件不会因为多次 require 而被重复插入
*/
`const id = ${JSON.stringify(filename)};`,
'if (document.head.getElementById(id)) return;',
'const style = document.createElement("style")',
'style.id = id;',
'style.textContent = css;',
'document.head.appendChild(style);',
].join('');
return module._compile(js, filename);
}
在上述代码中,参考了 style-loader 的思路,实现了一个简单的从 CSS 转化到 JavaScript 代码的操作。剩下的编译工作交给原来 Node.js 的流程去做就可以了(module._compile
部分的代码)。