针对上一篇文章我们的 bundler 只能加载 js,那么我们如何让它支持 css 那?
思路:
1). 我们的 bundler 只能加载 js
2). 我们想要加载 css
如果我们能把 css 变成 js, 那么就可以加载 css 了
1. 把 css 变成 js
复制 bundler_1.ts 为 bundler_css.ts
let code = readFileSync(filepath).toString()
if (/\.css$/.test(filepath)) {
code = `
const str = ${JSON.stringify(code)}
export default str
`
}
问题:上面的代码把 body {color: red} 变成
const str = "body {color: red}"; export default str
有什么 X 用?body 里的文字并不会变红啊
1.1 改进
if (/\.css$/.test(filepath)) { // 如果文件路径以 .css 结尾
code = `
const str = ${JSON.stringify(code)}
+ if (document) {
+ const style = document.createElement('style')
+ style.innerHTML = str
+ document.head.appendChild(style)
+ }
export default str
`
}
测试复制 project_1 为 project_css
新建一个style.css 文件,然后在index.js 里引入
- style.css
body {
color: red;
}
运行 node -r ts-node/register bundler_css.ts
然后直接在 dist 目录下创建 index.html 引入打包生成的文件
解决过程
- project_1 目录复制为 project_css
- bungler_1.ts 复制为 bundler_css.ts
- 改一下 bundler_css.ts
- 改一下 project_css 让 index.js 加载一个 css 文件
- 创建 index.html 测试 css 是否生效
2. 创建 css-loader
将上面的 code 代码抽离为单独的 loader
- 新建一个 loaders/css-loader.js 文件
const transform = (code) => `
const str = ${JSON.stringify(code)}
if (document) {
const style = document.createElement('style')
style.innerHTML = str
document.head.appendChild(style)
}
export default str
`
module.exports = transform
- 复制一个 bundler_css.ts 并命名为 bundler_css_loader.ts
将代码改为
if (/\.css$/.test(filepath)) {
code = require('./loaders/css-loader.js')(code)
}
2.1. 什么是 loader
1). loader 可以是一个普通函数
function transform(code) {
const code2 = doSomething(code)
return code2
}
module.exports = transform
2). loader 也可以是一个异步函数
async function transform(code) {
const code2 = await doSomething(code)
return code2
}
module.exports = transform
核心代码
为什么用 require 不用 import
- 主要是为了方便动态加载
3. 优化 loader
单一职责原则
webpack 里每个 loader 只做一件事
而我们的 css-loader 做了两件事:
1). 把 css 变为 js 字符串
2). 把 js 字符串放到 style 标签里
将我们的 loader 拆成 2 个
新建一个 style-loader
- loaders_2/style-loader.js
const transform = (code) => `
if (document) {
const style = document.createElement('style')
style.innerHTML = ${JSON.stringify(code)}
document.head.appendChild(style)
}
export default str
`
module.exports = transform
- loaders_2/css-loader.js
const transform = (code) => `
const str = ${JSON.stringify(code)}
export default str
`
module.exports = transform
- bundler_css_loader2.ts
if (/\.css$/.test(filepath)) {
code = require('./loaders_2/css-loader.js')(code)
code = require('./loaders_2/style-loader.js')(code)
}
运行 node -r ts-node/register bundler_css_loader2.ts
打包编译的代码如下
style.innerHTML 里多了很多我们不需要的代码,实际上我们只想要 style.innerHTML = "{color: red}"
3.1. 问题分析
- style-loader 不是转译
sass-loader、less-loader 这些 loader 是把代码从一种语言转译为另一种
因此将这样的 loader 连接起来不会出问题
但 style-loader 是在插入代码,不是转译,所以需要寻找插入时机和插入位置
插入代码的时机应该是在获取到 css-loader 的结果之后
插入代码的位置应该是在就代码的下面 - Webpack 官方 style-loader 的思路
style-loader 在 pitch 钩子里通过 css-loader 来 require 文件内容
然后在文件内容后面添加 injectStylesIntoStyleTag(content, ...) 代码