خلفية
في إحدى ليالي السبت كنت جالسًا وأبحث عن طرق لإنشاء مجموعة أدوات واجهة المستخدم باستخدام حزمة الويب. أستخدم 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")
}
}
}
منجز. نجمعها.
بعد الإنشاء ، تم العثور على ملفي Button.js و Dropdown.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.
البحث عن مسار بناء آخر بدون حزمة الويب
نذهب للحصول على مساعدة بابل بدون حزمة ويب. نكتب نصًا برمجيًا في package.json ، نحدد ملف التكوين ، الامتدادات ، الدليل الذي توجد به المكونات ، الدليل حيث يتم البناء:
{
//...package.json -
scripts: {
"build": "babel --config-file ./.babelrc --extensions '.jsx, .tsx' ./components --out-dir ./build"
}
}
يركض:
npm run build
Voila ، لدينا الآن ملفان Button.js ، Dropdown.js في دليل الإنشاء ، يوجد داخل الملفات ملف vanilla js مصمم بشكل جيد + بعض polyfills و requre الوحيد ("styles.scss") . من الواضح أن هذا لن يعمل في العرض التوضيحي ، وقم بإزالة استيراد الأنماط (في تلك اللحظة كنت أقضم على أمل أن أجد مكونًا إضافيًا لـ scss transpile) ، وقم بتجميعه مرة أخرى.
بعد التجميع ، لا يزال لدينا بعض JS لطيفة. دعنا نحاول مرة أخرى دمج المكون المُجمع في styleguidist:
```javascript
import Button from '../../build/Button'
<Button></Button>
```
المترجمة - يعمل. فقط زر بدون أنماط.
نحن نبحث عن مكون إضافي لـ transpile scss / sass
نعم ، تجميع المكونات يعمل ، المكونات تعمل ، يمكنك البناء ، النشر في npm أو رابطة العمل الخاصة بك. مع ذلك ، فقط احفظ الأنماط ... حسنًا ، ستساعدنا Google مرة أخرى (لا).
لم يعطني البحث عن المكونات الإضافية في Google أي نتائج. يُنشئ أحد المكونات الإضافية سلسلة من الأنماط ، بينما الآخر لا يعمل على الإطلاق ، بل ويتطلب استيراد طريقة العرض: استيراد أنماط من "styles.scss"
كان الأمل الوحيد لهذا المكون الإضافي: babel-plugin-transform-scss-import-to-string، لكنه فقط يولد خطًا من الأنماط (آه ... سبق أن قلت أعلاه. اللعنة ...) ثم ساء كل شيء ، لقد وصلت إلى الصفحة 6 في Google (والساعة بالفعل 3 صباحًا). ولن تكون هناك خيارات معينة للعثور على شيء ما. نعم ، وليس هناك ما يفكر فيه - إما webpack + sass-loader ، الذي يفعل ذلك بشكل سيء وليس لحالتي ، أو شيئًا آخر. الأعصاب ... قررت أن آخذ استراحة وشرب الشاي ، ما زلت لا أريد النوم بينما كنت أقوم بإعداد الشاي ، ظلت فكرة كتابة مكون إضافي لـ scss / sass transpile تطرأ على رأسي أكثر فأكثر. بينما كان السكر يتحرك ، رنين الملعقة في رأسي من حين لآخر كان يردد: "اكتب بلاجين". حسنًا ، قررت ، سأكتب ملحقًا.
البرنامج المساعد غير موجود. نكتب أنفسنا
أخذتُ babel-plugin-transform-scss-import-to-string المذكورة أعلاه كأساس للمكوِّن الإضافي الخاص بي . لقد فهمت تمامًا أنه سيكون هناك الآن بواسير مع شجرة AST ، وحيل أخرى. حسنا دعنا نذهب.
نجري الاستعدادات الأولية. نحتاج إلى node-sass و path ، بالإضافة إلى خطوط منتظمة للملفات والامتدادات. الفكرة هي:
- نحصل على مسار الملف بأنماط من سطر الاستيراد
- تحليل الأنماط إلى السلسلة عبر node-sass (بفضل babel-plugin-transform-scss-import-to-string)
- نقوم بإنشاء علامات نمط لكل عملية استيراد (يتم تشغيل المكون الإضافي babel في كل استيراد)
- من الضروري تحديد النمط الذي تم إنشاؤه بطريقة ما ، حتى لا يتم إلقاء نفس الشيء على كل عطسة يتم إعادة تحميلها على الساخن. دعونا ندفع في بعض السمات (data-sass-component) مع قيمة الملف الحالي واسم ورقة الأنماط. سيكون هناك شيء مثل هذا:
<style data-sass-component="Button_style"> .button { display: flex; } </style>
من أجل تطوير البرنامج المساعد واختباره على المشروع ، على مستوى دليل المكونات ، قمت بإنشاء دليل babel-plugin-transform-scss ، و stuffed package.json هناك وحشو دليل lib هناك ، وقد رميت به index.js بالفعل.
ماذا ستكون vkurse - تكوين Babel يتسلق خلف المكون الإضافي ، والذي تم تحديده في التوجيه الرئيسي في package.json ، لهذا كان علي حشره.نشير إلى:
{
//...package.json - , main
main: "lib/index.js"
}
بعد ذلك ، ادفع المسار إلى المكوّن الإضافي في ملف التكوين babel (.babelrc):
{
//
plugins: [
"./babel-plugin-transform-scss"
//
]
}
الآن ، دعونا نحشر بعض السحر في index.js.
المرحلة الأولى هي التحقق من استيراد ملف scss أو sass ، والحصول على اسم الملفات المستوردة ، والحصول على اسم ملف js (المكون) نفسه ، ونقل سلسلة scss أو sass إلى css. قمنا بتقسيم WebStorm إلى npm ، ثم تشغيل build من خلال مصحح أخطاء ، وتعيين نقاط توقف ، وإلقاء نظرة على وسيطات المسار والحالة ، واستخراج أسماء الملفات ، ومعالجتها باستخدام اللعنات:
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() || "";
}
}
}
نار. أول نجاح ، حصل على سطر css في transpiledContent. بعد ذلك ، أسوأ شيء - نحن نصعد إلى babeljs.io/docs/en/babel-types#api لواجهة برمجة التطبيقات على شجرة AST. نتسلق إلى 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 باستخدام هذا الهيكل ، ودخن كل هذا وكتابة بديل.
بعد لحظات قليلة ... بعد
ساعات ونصف ، من خلال علامات التبويب من ast إلى api من نوع بابل ، ثم في الكود ، كتبت بديلاً لاستيراد scss / sass. لن أقوم بتحليل شجرة آست و 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
),
[]
)
);
}
}
}
أفراح أخيرة
الصيحة !!! تم استبدال الاستيراد باستدعاء وظيفة تكدس النمط بهذا الزر في رأس المستند. وبعد ذلك فكرت ، ماذا لو بدأت هذا القارب بأكمله من خلال حزمة الويب ، قص لودر sass؟ هل ستعمل؟ حسنًا ، نحن جز ونفحص. أقوم بتشغيل التجميع باستخدام حزمة ويب ، في انتظار حدوث خطأ يجب أن أحدد محملًا لنوع الملف هذا ... ولكن لا يوجد خطأ ، يتم تجميع كل شيء. أفتح الصفحة ، وألقي نظرة ، والنمط عالق في رأس المستند. اتضح بشكل مثير للاهتمام ، لقد تخلصت أيضًا من 3 رافعات نمط (ابتسامة سعيدة جدًا).
إذا كنت مهتمًا بالمقال ، فيرجى دعمه بعلامة النجمة على جيثب .
أيضًا رابط لحزمة npm: www.npmjs.com/package/babel-plugin-transform-scss
ملاحظة: خارج المقالة ، تمت إضافة فحص لاستيراد النمط حسب النوعاستيراد الأنماط من "./styles.scss"