summaryrefslogtreecommitdiff
path: root/src/compiler/transform
diff options
context:
space:
mode:
authorGravatar Nate Moore <natemoo-re@users.noreply.github.com> 2021-04-30 16:33:35 -0500
committerGravatar GitHub <noreply@github.com> 2021-04-30 16:33:35 -0500
commit4df1347156cf2632ea2f3475d3a5f8f08d197cc3 (patch)
tree9d50de89dfe62827c32a8a4046120af4ab61dc0c /src/compiler/transform
parent1d498facc8f78a3ffbfecd05cc6ecd45e8a4a1ae (diff)
downloadastro-4df1347156cf2632ea2f3475d3a5f8f08d197cc3.tar.gz
astro-4df1347156cf2632ea2f3475d3a5f8f08d197cc3.tar.zst
astro-4df1347156cf2632ea2f3475d3a5f8f08d197cc3.zip
Migrate to `yarn` monorepo (#157)
* chore: use monorepo * chore: scaffold astro-scripts * chore: move tests inside packages/astro * chore: refactor tests, add scripts * chore: move parser to own module * chore: move runtime to packages/astro * fix: move parser to own package * test: fix prettier-plugin-astro tests * fix: tests * chore: update package-lock * chore: add changesets * fix: cleanup examples * fix: starter example * chore: update changeset config * chore: update changeset config * chore: setup changeset release workflow * chore: bump lockfiles * chore: prism => astro-prism * fix: tsc --emitDeclarationOnly * chore: final cleanup, switch to yarn * chore: add lerna * chore: update workflows to yarn * chore: update workflows * chore: remove lint workflow * chore: add astro-dev script * chore: add symlinked README
Diffstat (limited to 'src/compiler/transform')
-rw-r--r--src/compiler/transform/doctype.ts36
-rw-r--r--src/compiler/transform/index.ts100
-rw-r--r--src/compiler/transform/module-scripts.ts43
-rw-r--r--src/compiler/transform/postcss-scoped-styles/index.ts106
-rw-r--r--src/compiler/transform/prism.ts89
-rw-r--r--src/compiler/transform/styles.ts290
6 files changed, 0 insertions, 664 deletions
diff --git a/src/compiler/transform/doctype.ts b/src/compiler/transform/doctype.ts
deleted file mode 100644
index e871f5b48..000000000
--- a/src/compiler/transform/doctype.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { Transformer } from '../../@types/transformer';
-
-/** Transform <!doctype> tg */
-export default function (_opts: { filename: string; fileID: string }): Transformer {
- let hasDoctype = false;
-
- return {
- visitors: {
- html: {
- Element: {
- enter(node, parent, _key, index) {
- if (node.name === '!doctype') {
- hasDoctype = true;
- }
- if (node.name === 'html' && !hasDoctype) {
- const dtNode = {
- start: 0,
- end: 0,
- attributes: [{ type: 'Attribute', name: 'html', value: true, start: 0, end: 0 }],
- children: [],
- name: '!doctype',
- type: 'Element',
- };
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- parent.children!.splice(index, 0, dtNode);
- hasDoctype = true;
- }
- },
- },
- },
- },
- async finalize() {
- // Nothing happening here.
- },
- };
-}
diff --git a/src/compiler/transform/index.ts b/src/compiler/transform/index.ts
deleted file mode 100644
index 02a98709b..000000000
--- a/src/compiler/transform/index.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-import type { Ast, TemplateNode } from '../../parser/interfaces';
-import type { NodeVisitor, TransformOptions, Transformer, VisitorFn } from '../../@types/transformer';
-
-import { walk } from 'estree-walker';
-
-// Transformers
-import transformStyles from './styles.js';
-import transformDoctype from './doctype.js';
-import transformModuleScripts from './module-scripts.js';
-import transformCodeBlocks from './prism.js';
-
-interface VisitorCollection {
- enter: Map<string, VisitorFn[]>;
- leave: Map<string, VisitorFn[]>;
-}
-
-/** Add visitors to given collection */
-function addVisitor(visitor: NodeVisitor, collection: VisitorCollection, nodeName: string, event: 'enter' | 'leave') {
- if (typeof visitor[event] !== 'function') return;
- if (!collection[event]) collection[event] = new Map<string, VisitorFn[]>();
-
- const visitors = collection[event].get(nodeName) || [];
- visitors.push(visitor[event] as any);
- collection[event].set(nodeName, visitors);
-}
-
-/** Compile visitor actions from transformer */
-function collectVisitors(transformer: Transformer, htmlVisitors: VisitorCollection, cssVisitors: VisitorCollection, finalizers: Array<() => Promise<void>>) {
- if (transformer.visitors) {
- if (transformer.visitors.html) {
- for (const [nodeName, visitor] of Object.entries(transformer.visitors.html)) {
- addVisitor(visitor, htmlVisitors, nodeName, 'enter');
- addVisitor(visitor, htmlVisitors, nodeName, 'leave');
- }
- }
- if (transformer.visitors.css) {
- for (const [nodeName, visitor] of Object.entries(transformer.visitors.css)) {
- addVisitor(visitor, cssVisitors, nodeName, 'enter');
- addVisitor(visitor, cssVisitors, nodeName, 'leave');
- }
- }
- }
- finalizers.push(transformer.finalize);
-}
-
-/** Utility for formatting visitors */
-function createVisitorCollection() {
- return {
- enter: new Map<string, VisitorFn[]>(),
- leave: new Map<string, VisitorFn[]>(),
- };
-}
-
-/** Walk AST with collected visitors */
-function walkAstWithVisitors(tmpl: TemplateNode, collection: VisitorCollection) {
- walk(tmpl, {
- enter(node, parent, key, index) {
- if (collection.enter.has(node.type)) {
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- const fns = collection.enter.get(node.type)!;
- for (let fn of fns) {
- fn.call(this, node, parent, key, index);
- }
- }
- },
- leave(node, parent, key, index) {
- if (collection.leave.has(node.type)) {
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- const fns = collection.leave.get(node.type)!;
- for (let fn of fns) {
- fn.call(this, node, parent, key, index);
- }
- }
- },
- });
-}
-
-/**
- * Transform
- * Step 2/3 in Astro SSR.
- * Transform is the point at which we mutate the AST before sending off to
- * Codegen, and then to Snowpack. In some ways, it‘s a preprocessor.
- */
-export async function transform(ast: Ast, opts: TransformOptions) {
- const htmlVisitors = createVisitorCollection();
- const cssVisitors = createVisitorCollection();
- const finalizers: Array<() => Promise<void>> = [];
-
- const optimizers = [transformStyles(opts), transformDoctype(opts), transformModuleScripts(opts), transformCodeBlocks(ast.module)];
-
- for (const optimizer of optimizers) {
- collectVisitors(optimizer, htmlVisitors, cssVisitors, finalizers);
- }
-
- walkAstWithVisitors(ast.css, cssVisitors);
- walkAstWithVisitors(ast.html, htmlVisitors);
-
- // Run all of the finalizer functions in parallel because why not.
- await Promise.all(finalizers.map((fn) => fn()));
-}
diff --git a/src/compiler/transform/module-scripts.ts b/src/compiler/transform/module-scripts.ts
deleted file mode 100644
index aff1ec4f6..000000000
--- a/src/compiler/transform/module-scripts.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import type { Transformer } from '../../@types/transformer';
-import type { CompileOptions } from '../../@types/compiler';
-
-import path from 'path';
-import { getAttrValue, setAttrValue } from '../../ast.js';
-
-/** Transform <script type="module"> */
-export default function ({ compileOptions, filename }: { compileOptions: CompileOptions; filename: string; fileID: string }): Transformer {
- const { astroConfig } = compileOptions;
- const { astroRoot } = astroConfig;
- const fileUrl = new URL(`file://${filename}`);
-
- return {
- visitors: {
- html: {
- Element: {
- enter(node) {
- let name = node.name;
- if (name !== 'script') {
- return;
- }
-
- let type = getAttrValue(node.attributes, 'type');
- if (type !== 'module') {
- return;
- }
-
- let src = getAttrValue(node.attributes, 'src');
- if (!src || !src.startsWith('.')) {
- return;
- }
-
- const srcUrl = new URL(src, fileUrl);
- const fromAstroRoot = path.posix.relative(astroRoot.pathname, srcUrl.pathname);
- const absoluteUrl = `/_astro/${fromAstroRoot}`;
- setAttrValue(node.attributes, 'src', absoluteUrl);
- },
- },
- },
- },
- async finalize() {},
- };
-}
diff --git a/src/compiler/transform/postcss-scoped-styles/index.ts b/src/compiler/transform/postcss-scoped-styles/index.ts
deleted file mode 100644
index 23350869c..000000000
--- a/src/compiler/transform/postcss-scoped-styles/index.ts
+++ /dev/null
@@ -1,106 +0,0 @@
-import { Declaration, Plugin } from 'postcss';
-
-interface AstroScopedOptions {
- className: string;
-}
-
-interface Selector {
- start: number;
- end: number;
- value: string;
-}
-
-const CSS_SEPARATORS = new Set([' ', ',', '+', '>', '~']);
-const KEYFRAME_PERCENT = /\d+\.?\d*%/;
-
-/** HTML tags that should never get scoped classes */
-export const NEVER_SCOPED_TAGS = new Set<string>(['base', 'body', 'font', 'frame', 'frameset', 'head', 'html', 'link', 'meta', 'noframes', 'noscript', 'script', 'style', 'title']);
-
-/**
- * Scope Rules
- * Given a selector string (`.btn>span,.nav>span`), add an additional CSS class to every selector (`.btn.myClass>span.myClass,.nav.myClass>span.myClass`)
- * @param {string} selector The minified selector string to parse. Cannot contain arbitrary whitespace (other than child selector syntax).
- * @param {string} className The CSS class to apply.
- */
-export function scopeRule(selector: string, className: string) {
- // if this is a keyframe keyword, return original selector
- if (selector === 'from' || selector === 'to' || KEYFRAME_PERCENT.test(selector)) {
- return selector;
- }
-
- // For everything else, parse & scope
- const c = className.replace(/^\.?/, '.'); // make sure class always has leading '.'
- const selectors: Selector[] = [];
- let ss = selector; // final output
-
- // Pass 1: parse selector string; extract top-level selectors
- {
- let start = 0;
- let lastValue = '';
- let parensOpen = false;
- for (let n = 0; n < ss.length; n++) {
- const isEnd = n === selector.length - 1;
- if (selector[n] === '(') parensOpen = true;
- if (selector[n] === ')') parensOpen = false;
- if (isEnd || (parensOpen === false && CSS_SEPARATORS.has(selector[n]))) {
- lastValue = selector.substring(start, isEnd ? undefined : n);
- if (!lastValue) continue;
- selectors.push({ start, end: isEnd ? n + 1 : n, value: lastValue });
- start = n + 1;
- }
- }
- }
-
- // Pass 2: starting from end, transform selectors w/ scoped class
- for (let i = selectors.length - 1; i >= 0; i--) {
- const { start, end, value } = selectors[i];
- const head = ss.substring(0, start);
- const tail = ss.substring(end);
-
- // replace '*' with className
- if (value === '*') {
- ss = head + c + tail;
- continue;
- }
-
- // leave :global() alone!
- if (value.startsWith(':global(')) {
- ss =
- head +
- ss
- .substring(start, end)
- .replace(/^:global\(/, '')
- .replace(/\)$/, '') +
- tail;
- continue;
- }
-
- // don‘t scope body, title, etc.
- if (NEVER_SCOPED_TAGS.has(value)) {
- ss = head + value + tail;
- continue;
- }
-
- // scope everything else
- let newSelector = ss.substring(start, end);
- const pseudoIndex = newSelector.indexOf(':');
- if (pseudoIndex > 0) {
- // if there‘s a pseudoclass (:focus)
- ss = head + newSelector.substring(start, pseudoIndex) + c + newSelector.substr(pseudoIndex) + tail;
- } else {
- ss = head + newSelector + c + tail;
- }
- }
-
- return ss;
-}
-
-/** PostCSS Scope plugin */
-export default function astroScopedStyles(options: AstroScopedOptions): Plugin {
- return {
- postcssPlugin: '@astro/postcss-scoped-styles',
- Rule(rule) {
- rule.selector = scopeRule(rule.selector, options.className);
- },
- };
-}
diff --git a/src/compiler/transform/prism.ts b/src/compiler/transform/prism.ts
deleted file mode 100644
index 1bb024a84..000000000
--- a/src/compiler/transform/prism.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-import type { Transformer } from '../../@types/transformer';
-import type { Script } from '../../parser/interfaces';
-import { getAttrValue } from '../../ast.js';
-
-const PRISM_IMPORT = `import Prism from 'astro/components/Prism.astro';\n`;
-const prismImportExp = /import Prism from ['"]astro\/components\/Prism.astro['"]/;
-/** escaping code samples that contain template string replacement parts, ${foo} or example. */
-function escape(code: string) {
- return code.replace(/[`$]/g, (match) => {
- return '\\' + match;
- });
-}
-/** default export - Transform prism */
-export default function (module: Script): Transformer {
- let usesPrism = false;
-
- return {
- visitors: {
- html: {
- Element: {
- enter(node) {
- if (node.name !== 'code') return;
- const className = getAttrValue(node.attributes, 'class') || '';
- const classes = className.split(' ');
-
- let lang;
- for (let cn of classes) {
- const matches = /language-(.+)/.exec(cn);
- if (matches) {
- lang = matches[1];
- }
- }
-
- if (!lang) return;
-
- let code;
- if (node.children?.length) {
- code = node.children[0].data;
- }
-
- const repl = {
- start: 0,
- end: 0,
- type: 'InlineComponent',
- name: 'Prism',
- attributes: [
- {
- type: 'Attribute',
- name: 'lang',
- value: [
- {
- type: 'Text',
- raw: lang,
- data: lang,
- },
- ],
- },
- {
- type: 'Attribute',
- name: 'code',
- value: [
- {
- type: 'MustacheTag',
- expression: {
- type: 'Expression',
- codeChunks: ['`' + escape(code) + '`'],
- children: [],
- },
- },
- ],
- },
- ],
- children: [],
- };
-
- this.replace(repl);
- usesPrism = true;
- },
- },
- },
- },
- async finalize() {
- // Add the Prism import if needed.
- if (usesPrism && !prismImportExp.test(module.content)) {
- module.content = PRISM_IMPORT + module.content;
- }
- },
- };
-}
diff --git a/src/compiler/transform/styles.ts b/src/compiler/transform/styles.ts
deleted file mode 100644
index 53585651f..000000000
--- a/src/compiler/transform/styles.ts
+++ /dev/null
@@ -1,290 +0,0 @@
-import crypto from 'crypto';
-import fs from 'fs';
-import { createRequire } from 'module';
-import path from 'path';
-import { fileURLToPath } from 'url';
-import autoprefixer from 'autoprefixer';
-import postcss, { Plugin } from 'postcss';
-import postcssKeyframes from 'postcss-icss-keyframes';
-import findUp from 'find-up';
-import sass from 'sass';
-import type { RuntimeMode } from '../../@types/astro';
-import type { TransformOptions, Transformer } from '../../@types/transformer';
-import type { TemplateNode } from '../../parser/interfaces';
-import { debug } from '../../logger.js';
-import astroScopedStyles, { NEVER_SCOPED_TAGS } from './postcss-scoped-styles/index.js';
-
-type StyleType = 'css' | 'scss' | 'sass' | 'postcss';
-
-declare global {
- interface ImportMeta {
- /** https://nodejs.org/api/esm.html#esm_import_meta_resolve_specifier_parent */
- resolve(specifier: string, parent?: string): Promise<any>;
- }
-}
-
-const getStyleType: Map<string, StyleType> = new Map([
- ['.css', 'css'],
- ['.pcss', 'postcss'],
- ['.sass', 'sass'],
- ['.scss', 'scss'],
- ['css', 'css'],
- ['sass', 'sass'],
- ['scss', 'scss'],
- ['text/css', 'css'],
- ['text/sass', 'sass'],
- ['text/scss', 'scss'],
-]);
-
-/** Should be deterministic, given a unique filename */
-function hashFromFilename(filename: string): string {
- const hash = crypto.createHash('sha256');
- return hash
- .update(filename.replace(/\\/g, '/'))
- .digest('base64')
- .toString()
- .replace(/[^A-Za-z0-9-]/g, '')
- .substr(0, 8);
-}
-
-export interface StyleTransformResult {
- css: string;
- type: StyleType;
-}
-
-interface StylesMiniCache {
- nodeModules: Map<string, string>; // filename: node_modules location
- tailwindEnabled?: boolean; // cache once per-run
-}
-
-/** Simple cache that only exists in memory per-run. Prevents the same lookups from happening over and over again within the same build or dev server session. */
-const miniCache: StylesMiniCache = {
- nodeModules: new Map<string, string>(),
-};
-
-export interface TransformStyleOptions {
- type?: string;
- filename: string;
- scopedClass: string;
- mode: RuntimeMode;
-}
-
-/** given a class="" string, does it contain a given class? */
-function hasClass(classList: string, className: string): boolean {
- if (!className) return false;
- for (const c of classList.split(' ')) {
- if (className === c.trim()) return true;
- }
- return false;
-}
-
-/** Convert styles to scoped CSS */
-async function transformStyle(code: string, { type, filename, scopedClass, mode }: TransformStyleOptions): Promise<StyleTransformResult> {
- let styleType: StyleType = 'css'; // important: assume CSS as default
- if (type) {
- styleType = getStyleType.get(type) || styleType;
- }
-
- // add file path to includePaths
- let includePaths: string[] = [path.dirname(filename)];
-
- // include node_modules to includePaths (allows @use-ing node modules, if it can be located)
- const cachedNodeModulesDir = miniCache.nodeModules.get(filename);
- if (cachedNodeModulesDir) {
- includePaths.push(cachedNodeModulesDir);
- } else {
- const nodeModulesDir = await findUp('node_modules', { type: 'directory', cwd: path.dirname(filename) });
- if (nodeModulesDir) {
- miniCache.nodeModules.set(filename, nodeModulesDir);
- includePaths.push(nodeModulesDir);
- }
- }
-
- // 1. Preprocess (currently only Sass supported)
- let css = '';
- switch (styleType) {
- case 'css': {
- css = code;
- break;
- }
- case 'sass':
- case 'scss': {
- css = sass.renderSync({ data: code, includePaths }).css.toString('utf8');
- break;
- }
- default: {
- throw new Error(`Unsupported: <style lang="${styleType}">`);
- }
- }
-
- // 2. Post-process (PostCSS)
- const postcssPlugins: Plugin[] = [];
-
- // 2a. Tailwind (only if project uses Tailwind)
- if (miniCache.tailwindEnabled) {
- try {
- const require = createRequire(import.meta.url);
- const tw = require.resolve('tailwindcss', { paths: [import.meta.url, process.cwd()] });
- postcssPlugins.push(require(tw) as any);
- } catch (err) {
- // eslint-disable-next-line no-console
- console.error(err);
- throw new Error(`tailwindcss not installed. Try running \`npm install tailwindcss\` and trying again.`);
- }
- }
-
- // 2b. Astro scoped styles (always on)
- postcssPlugins.push(astroScopedStyles({ className: scopedClass }));
-
- // 2c. Scoped @keyframes
- postcssPlugins.push(
- postcssKeyframes({
- generateScopedName(keyframesName) {
- return `${keyframesName}-${scopedClass}`;
- },
- })
- );
-
- // 2d. Autoprefixer (always on)
- postcssPlugins.push(autoprefixer());
-
- // 2e. Run PostCSS
- css = await postcss(postcssPlugins)
- .process(css, { from: filename, to: undefined })
- .then((result) => result.css);
-
- return { css, type: styleType };
-}
-
-/** Transform <style> tags */
-export default function transformStyles({ compileOptions, filename, fileID }: TransformOptions): Transformer {
- const styleNodes: TemplateNode[] = []; // <style> tags to be updated
- const styleTransformPromises: Promise<StyleTransformResult>[] = []; // async style transform results to be finished in finalize();
- const scopedClass = `astro-${hashFromFilename(fileID)}`; // this *should* generate same hash from fileID every time
-
- // find Tailwind config, if first run (cache for subsequent runs)
- if (miniCache.tailwindEnabled === undefined) {
- const tailwindNames = ['tailwind.config.js', 'tailwind.config.mjs'];
- for (const loc of tailwindNames) {
- const tailwindLoc = path.join(fileURLToPath(compileOptions.astroConfig.projectRoot), loc);
- if (fs.existsSync(tailwindLoc)) {
- miniCache.tailwindEnabled = true; // Success! We have a Tailwind config file.
- debug(compileOptions.logging, 'tailwind', 'Found config. Enabling.');
- break;
- }
- }
- if (miniCache.tailwindEnabled !== true) miniCache.tailwindEnabled = false; // We couldn‘t find one; mark as false
- debug(compileOptions.logging, 'tailwind', 'No config found. Skipping.');
- }
-
- return {
- visitors: {
- html: {
- Element: {
- enter(node) {
- // 1. if <style> tag, transform it and continue to next node
- if (node.name === 'style') {
- // Same as ast.css (below)
- const code = Array.isArray(node.children) ? node.children.map(({ data }: any) => data).join('\n') : '';
- if (!code) return;
- const langAttr = (node.attributes || []).find(({ name }: any) => name === 'lang');
- styleNodes.push(node);
- styleTransformPromises.push(
- transformStyle(code, {
- type: (langAttr && langAttr.value[0] && langAttr.value[0].data) || undefined,
- filename,
- scopedClass,
- mode: compileOptions.mode,
- })
- );
- return;
- }
-
- // 2. add scoped HTML classes
- if (NEVER_SCOPED_TAGS.has(node.name)) return; // only continue if this is NOT a <script> tag, etc.
- // Note: currently we _do_ scope web components/custom elements. This seems correct?
-
- if (!node.attributes) node.attributes = [];
- const classIndex = node.attributes.findIndex(({ name }: any) => name === 'class');
- if (classIndex === -1) {
- // 3a. element has no class="" attribute; add one and append scopedClass
- node.attributes.push({ start: -1, end: -1, type: 'Attribute', name: 'class', value: [{ type: 'Text', raw: scopedClass, data: scopedClass }] });
- } else {
- // 3b. element has class=""; append scopedClass
- const attr = node.attributes[classIndex];
- for (let k = 0; k < attr.value.length; k++) {
- if (attr.value[k].type === 'Text') {
- // don‘t add same scopedClass twice
- if (!hasClass(attr.value[k].data, scopedClass)) {
- // string literal
- attr.value[k].raw += ' ' + scopedClass;
- attr.value[k].data += ' ' + scopedClass;
- }
- } else if (attr.value[k].type === 'MustacheTag' && attr.value[k]) {
- // don‘t add same scopedClass twice (this check is a little more basic, but should suffice)
- if (!attr.value[k].expression.codeChunks[0].includes(`' ${scopedClass}'`)) {
- // MustacheTag
- // FIXME: this won't work when JSX element can appear in attributes (rare but possible).
- attr.value[k].expression.codeChunks[0] = `(${attr.value[k].expression.codeChunks[0]}) + ' ${scopedClass}'`;
- }
- }
- }
- }
- },
- },
- },
- // CSS: compile styles, apply CSS Modules scoping
- css: {
- Style: {
- enter(node) {
- // Same as ast.html (above)
- // Note: this is duplicated from html because of the compiler we‘re using; in a future version we should combine these
- if (!node.content || !node.content.styles) return;
- const code = node.content.styles;
- const langAttr = (node.attributes || []).find(({ name }: any) => name === 'lang');
- styleNodes.push(node);
- styleTransformPromises.push(
- transformStyle(code, {
- type: (langAttr && langAttr.value[0] && langAttr.value[0].data) || undefined,
- filename,
- scopedClass,
- mode: compileOptions.mode,
- })
- );
- },
- },
- },
- },
- async finalize() {
- const styleTransforms = await Promise.all(styleTransformPromises);
-
- styleTransforms.forEach((result, n) => {
- if (styleNodes[n].attributes) {
- // 1. Replace with final CSS
- const isHeadStyle = !styleNodes[n].content;
- if (isHeadStyle) {
- // Note: <style> tags in <head> have different attributes/rules, because of the parser. Unknown why
- (styleNodes[n].children as any) = [{ ...(styleNodes[n].children as any)[0], data: result.css }];
- } else {
- styleNodes[n].content.styles = result.css;
- }
-
- // 2. Update <style> attributes
- const styleTypeIndex = styleNodes[n].attributes.findIndex(({ name }: any) => name === 'type');
- // add type="text/css"
- if (styleTypeIndex !== -1) {
- styleNodes[n].attributes[styleTypeIndex].value[0].raw = 'text/css';
- styleNodes[n].attributes[styleTypeIndex].value[0].data = 'text/css';
- } else {
- styleNodes[n].attributes.push({ name: 'type', type: 'Attribute', value: [{ type: 'Text', raw: 'text/css', data: 'text/css' }] });
- }
- // remove lang="*"
- const styleLangIndex = styleNodes[n].attributes.findIndex(({ name }: any) => name === 'lang');
- if (styleLangIndex !== -1) styleNodes[n].attributes.splice(styleLangIndex, 1);
- // TODO: add data-astro for later
- // styleNodes[n].attributes.push({ name: 'data-astro', type: 'Attribute', value: true });
- }
- });
- },
- };
-}