背景
一个星期六晚上,我坐在那里,寻找使用webpack构建UI-Kit的方法。作为UI-kit演示,我使用styleguidst。当然,webpack很聪明,它将工作目录中的所有文件塞进一个包中,从那里到处都是一遍又一遍的事情。
我创建了一个entry.js文件,在那里导入了所有组件,然后从那里导出。看来一切都很好。
import Button from 'components/Button'
import Dropdown from 'components/Dropdown '
export {
Button,
Dropdown
}
组装完所有这些之后,我得到了output.js,正如预期的那样,其中所有-堆中的所有组件都在一个文件中。这里出现了一个问题:
我如何才能分别收集所有按钮,下拉菜单等以导入其他项目?但我也想将其作为软件包上传到npm。
嗯...让我们开始吧。
多个条目
当然,首先想到的是解析工作目录中的所有组件。我不得不用Google来分析文件,因为我很少使用NodeJS。发现这样的事情水珠。
我们开车写了多个条目。
const { basename, join, resolve } = require("path");
const glob = require("glob");
const componentFileRegEx = /\.(j|t)s(x)?$/;
const sassFileRegEx = /\s[ac]ss$/;
const getComponentsEntries = (pattern) => {
const entries = {};
glob.sync(pattern).forEach(file => {
const outFile = basename (file);
const entryName = outFile.replace(componentFileRegEx, "");
entries[entryName] = join(__dirname, file);
})
return entries;
}
module.exports = {
entry: getComponentsEntries("./components/**/*.tsx"),
output: {
filename: "[name].js",
path: resolve(__dirname, "build")
},
module: {
rules: [
{
test: componentFileRegEx,
loader: "babel-loader",
exclude: /node_modules/
},
{
test: sassFileRegEx,
use: ["style-loader", "css-loader", "sass-loader"]
}
]
}
resolve: {
extensions: [".js", ".ts", ".tsx", ".jsx"],
alias: {
components: resolve(__dirname, "components")
}
}
}
做完了 我们收集。
构建之后,有2个Button.js文件,Dropdown.js进入了构建目录-让我们看看内部。许可证中包含react.production.min.js,难以阅读的精简代码以及很多废话。好吧,让我们尝试使用按钮。
在按钮的演示文件中,将导入更改为从构建目录导入。
这是一个简单的按钮演示在styleguidist中的样子-Button.md
```javascript
import Button from '../../build/Button'
<Button></Button>
```
我们来看一下IR按钮... 在这个阶段,通过webpack进行收集的想法和愿望已经消失了。
Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
寻找没有webpack的另一个构建路径
我们向没有webpack的通天塔寻求帮助。我们在package.json中编写一个脚本,指定配置文件,扩展名,组件所在的目录,构建的目录:
{
//...package.json -
scripts: {
"build": "babel --config-file ./.babelrc --extensions '.jsx, .tsx' ./components --out-dir ./build"
}
}
跑:
npm run build
瞧,我们在build目录中有2个文件Button.js,Dropdown.js,文件中有一个设计精美的vanilla js +一些polyfills和一个孤独的要求(“ styles.scss”)。显然,这在演示中将不起作用,删除样式的导入(那时候我很希望能找到用于scss transpile的插件),然后再次收集它。
组装后,我们还有一些不错的JS。让我们再次尝试将组装的组件集成到styleguidist中:
```javascript
import Button from '../../build/Button'
<Button></Button>
```
编译-有效。只有一个没有样式的按钮。
我们正在寻找用于Transsile Scss / Sass的插件
是的,组件的组装工作正常,组件正在工作,您可以在npm中构建,发布或自己的工作关系。不过,只需保存样式即可。好的,Google会再次帮助我们(否)。
谷歌搜索插件没有给我带来任何结果。一个插件从样式生成一个字符串,另一个插件根本不起作用,甚至需要导入视图:从“ styles.scss”导入样式
唯一的希望是此插件:babel-plugin-transform-scss-import-to-string,但是它只是根据样式生成一个字符串(啊...我在上面说过。该死的...)。然后一切都变得更糟了,我到达了Google的第6页(现在已经是凌晨3点了)。并且没有找到特定内容的特定选择。是的,没有什么可考虑的-webpack + sass-loader很讨厌这样做,而不是针对我的情况,或者其他。神经...我决定休息一下,喝点茶,我还是不想睡觉。在我泡茶的时候,为scss / sass transpile编写插件的想法越来越多地涌入我的脑海。当糖在搅动时,偶尔在我头上的汤匙响起,回响道:“写明信。” 好的,我决定写一个插件。
找不到插件。我们写自己
我将上面提到的babel-plugin-transform-scss-import-to-string作为我的plugin的基础。我完全理解,现在会有带有AST树的痔疮和其他技巧。好吧走吧
我们做准备。我们需要节点资源和路径,以及文件和扩展名的常规行。这个想法是这样的:
- 我们从导入行获取具有样式的文件路径
- 通过node-sass将样式解析为字符串(感谢babel-plugin-transform-scss-import-to-string)
- 我们为每个导入创建样式标签(每次导入都会启动babel插件)
- 有必要以某种方式标识所创建的样式,以免在每次热装打喷嚏时都扔相同的东西。让我们用当前文件的值和样式表的名称向它推送一些属性(data-sass-component)。会有这样的事情:
<style data-sass-component="Button_style"> .button { display: flex; } </style>
为了开发插件并在项目上对其进行测试,在components目录的级别上,我创建了一个babel-plugin-transform-scss目录,在其中填充package.json并在其中填充lib目录,并且我已经将index.js放入其中。
您将是什么-Babel配置位于package.json的main指令中指定的插件后面,为此,我不得不将其塞入。我们指出:
{
//...package.json - , main
main: "lib/index.js"
}
然后,将插件的路径推入babel配置(.babelrc):
{
//
plugins: [
"./babel-plugin-transform-scss"
//
]
}
现在,让我们将一些魔法塞入index.js。
第一步是检查scss或sass文件的导入,获取导入文件的名称,获取js文件(组件)本身的名称,然后将scss或sass字符串传输到css。我们将WebStorm切入npm,通过调试器运行构建,设置断点,查看路径和状态参数并获取文件名,并使用curses处理它们:
const { resolve, dirname, join } = require("path");
const { renderSync } = require("node-sass");
const regexps = {
sassFile: /([A-Za-z0-9]+).s[ac]ss/g,
sassExt: /\.s[ac]ss$/,
currentFile: /([A-Za-z0-9]+).(t|j)s(x)/g,
currentFileExt: /.(t|j)s(x)/g
};
function transformScss(babel) {
const { types: t } = babel;
return {
name: "babel-plugin-transform-scss",
visitor: {
ImportDeclaration(path, state) {
/**
* , scss/sass
*/
if (!regexps.sassExt.test(path.node.source.value)) return;
const sassFileNameMatch = path.node.source.value.match(
regexps.sassFile
);
/**
* scss/sass js
*/
const sassFileName = sassFileNameMatch[0].replace(regexps.sassExt, "");
const file = this.filename.match(regexps.currentFile);
const filename = `${file[0].replace(
regexps.currentFileExt,
""
)}_${sassFileName}`;
/**
*
* scss/sass , css
*/
const scssFileDirectory = resolve(dirname(state.file.opts.filename));
const fullScssFilePath = join(
scssFileDirectory,
path.node.source.value
);
const projectRoot = process.cwd();
const nodeModulesPath = join(projectRoot, "node_modules");
const sassDefaults = {
file: fullScssFilePath,
sourceMap: false,
includePaths: [nodeModulesPath, scssFileDirectory, projectRoot]
};
const sassResult = renderSync({ ...sassDefaults, ...state.opts });
const transpiledContent = sassResult.css.toString() || "";
}
}
}
火。第一次成功,在transpiledContent中获得了CSS行。接下来,最糟糕的事情-我们进入AST树上的API的babeljs.io/docs/en/babel-types#api。我们进入astexplorer.net并编写将样式表推入头部的代码。
在astexplorer.net中,编写一个将在样式导入位置调用的自调用函数:
(function(){
const styles = "generated transpiledContent" // ".button {/n display: flex; /n}/n"
const fileName = "generated_attributeValue" //Button_style
const element = document.querySelector("style[data-sass-component='fileName']")
if(!element){
const styleBlock = document.createElement("style")
styleBlock.innerHTML = styles
styleBlock.setAttribute("data-sass-component", fileName)
document.head.appendChild(styleBlock)
}
})()
在AST资源管理器中,在行,声明,文字的左侧戳一戳-在树的右侧查看声明的结构,我们使用此结构爬到babeljs.io/docs/en/babel-types#api中,吸取所有内容并编写替换内容。
过了一会儿...
1-1.5小时后,从ast到babel-types api遍历选项卡,然后进入代码,我编写了scss / sass导入的替代项。我不会分别解析ast树和babel类型的api,否则会有更多的字母。我立即显示结果:
const { resolve, dirname, join } = require("path");
const { renderSync } = require("node-sass");
const regexps = {
sassFile: /([A-Za-z0-9]+).s[ac]ss/g,
sassExt: /\.s[ac]ss$/,
currentFile: /([A-Za-z0-9]+).(t|j)s(x)/g,
currentFileExt: /.(t|j)s(x)/g
};
function transformScss(babel) {
const { types: t } = babel;
return {
name: "babel-plugin-transform-scss",
visitor: {
ImportDeclaration(path, state) {
/**
* , scss/sass
*/
if (!regexps.sassExt.test(path.node.source.value)) return;
const sassFileNameMatch = path.node.source.value.match(
regexps.sassFile
);
/**
* scss/sass js
*/
const sassFileName = sassFileNameMatch[0].replace(regexps.sassExt, "");
const file = this.filename.match(regexps.currentFile);
const filename = `${file[0].replace(
regexps.currentFileExt,
""
)}_${sassFileName}`;
/**
*
* scss/sass , css
*/
const scssFileDirectory = resolve(dirname(state.file.opts.filename));
const fullScssFilePath = join(
scssFileDirectory,
path.node.source.value
);
const projectRoot = process.cwd();
const nodeModulesPath = join(projectRoot, "node_modules");
const sassDefaults = {
file: fullScssFilePath,
sourceMap: false,
includePaths: [nodeModulesPath, scssFileDirectory, projectRoot]
};
const sassResult = renderSync({ ...sassDefaults, ...state.opts });
const transpiledContent = sassResult.css.toString() || "";
/**
* , AST Explorer
* replaceWith path.
*/
path.replaceWith(
t.callExpression(
t.functionExpression(
t.identifier(""),
[],
t.blockStatement(
[
t.variableDeclaration("const", [
t.variableDeclarator(
t.identifier("styles"),
t.stringLiteral(transpiledContent)
)
]),
t.variableDeclaration("const", [
t.variableDeclarator(
t.identifier("fileName"),
t.stringLiteral(filename)
)
]),
t.variableDeclaration("const", [
t.variableDeclarator(
t.identifier("element"),
t.callExpression(
t.memberExpression(
t.identifier("document"),
t.identifier("querySelector")
),
[
t.stringLiteral(
`style[data-sass-component='${filename}']`
)
]
)
)
]),
t.ifStatement(
t.unaryExpression("!", t.identifier("element"), true),
t.blockStatement(
[
t.variableDeclaration("const", [
t.variableDeclarator(
t.identifier("styleBlock"),
t.callExpression(
t.memberExpression(
t.identifier("document"),
t.identifier("createElement")
),
[t.stringLiteral("style")]
)
)
]),
t.expressionStatement(
t.assignmentExpression(
"=",
t.memberExpression(
t.identifier("styleBlock"),
t.identifier("innerHTML")
),
t.identifier("styles")
)
),
t.expressionStatement(
t.callExpression(
t.memberExpression(
t.identifier("styleBlock"),
t.identifier("setAttribute")
),
[
t.stringLiteral("data-sass-component"),
t.identifier("fileName")
]
)
),
t.expressionStatement(
t.callExpression(
t.memberExpression(
t.memberExpression(
t.identifier("document"),
t.identifier("head"),
false
),
t.identifier("appendChild"),
false
),
[t.identifier("styleBlock")]
)
)
],
[]
),
null
)
],
[]
),
false,
false
),
[]
)
);
}
}
}
最后的欢乐
哇!导入被对函数的调用所代替,该函数将该按钮的样式推入文档的头部。然后我想,如果我通过Webpack开始整个皮划艇,修剪Sass-Loader,该怎么办?能行吗 好吧,我们割草并检查。我使用webpack启动程序集,等待错误,我必须为此文件类型定义一个加载器...但是没有错误,所有内容都已组装好。我打开页面,外观和样式都停留在文档的头部。有趣的是,我还摆脱了3种样式的装载机(非常开心的笑容)。
如果您对本文感兴趣,请在github上用星号支持。
也是npm软件包的链接:www.npmjs.com/package/babel-plugin-transform-scss
注意:在本文之外,添加了按类型导入样式的检查从'./styles.scss'导入样式