diff --git a/.eslintignore b/.eslintignore index 849ddff..26a203e 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,2 @@ dist/ +add_esm_import_extensions.mjs \ No newline at end of file diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..e0325e5 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v16.17.1 diff --git a/History.md b/History.md index ab51baf..9bea7a3 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,14 @@ +4.1.0 / 2023-01-25 +================== + + * Support ESM Modules + +4.0.2 / 2023-01-12 +================== + + * #71 : @import does not work if url contains ';' + * #77 : Regression in selector parsing: Attribute selectors not parsed correctly + 4.0.1 / 2022-08-03 ================== diff --git a/Readme.md b/Readme.md index 2eef21a..4538720 100644 --- a/Readme.md +++ b/Readme.md @@ -6,19 +6,19 @@ CSS parser / stringifier. ## Installation - $ npm install css + $ npm install @adobe/css-tools ## Usage ```js -var css = require('css'); -var obj = css.parse('body { font-size: 12px; }', options); -css.stringify(obj, options); +import { parse, stringify } from '@adobe/css-tools' +let obj = parse('body { font-size: 12px; }', options); +let css = stringify(obj, options); ``` ## API -### css.parse(code, [options]) +### parse(code, [options]) Accepts a CSS string and returns an AST `object`. @@ -28,7 +28,7 @@ Accepts a CSS string and returns an AST `object`. - source: the path to the file containing `css`. Makes errors and source maps more helpful, by letting them know where code comes from. -### css.stringify(object, [options]) +### stringify(object, [options]) Accepts an AST `object` (as `css.parse` produces) and returns a CSS string. @@ -40,9 +40,9 @@ Accepts an AST `object` (as `css.parse` produces) and returns a CSS string. ### Example ```js -var ast = css.parse('body { font-size: 12px; }', { source: 'source.css' }); +var ast = parse('body { font-size: 12px; }', { source: 'source.css' }); -var css = css.stringify(ast); +var css = stringify(ast); ``` ### Errors diff --git a/add_esm_import_extensions.mjs b/add_esm_import_extensions.mjs new file mode 100644 index 0000000..0b37be5 --- /dev/null +++ b/add_esm_import_extensions.mjs @@ -0,0 +1,105 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +// https://gist.github.com/lovasoa/8691344 +async function* walk(dir) { + for await (const d of await fs.promises.opendir(dir)) { + const entry = path.join(dir, d.name); + if (d.isDirectory()) { + yield* walk(entry); + } else if (d.isFile()) { + yield entry; + } + } +} + +function resolveImportPath(sourceFile, importPath, options) { + const sourceFileAbs = path.resolve(process.cwd(), sourceFile); + const root = path.dirname(sourceFileAbs); + const {moduleFilter = defaultModuleFilter} = options; + + if (moduleFilter(importPath)) { + const importPathAbs = path.resolve(root, importPath); + const possiblePath = [ + path.resolve(importPathAbs, './index.ts'), + path.resolve(importPathAbs, './index.js'), + importPathAbs + '.ts', + importPathAbs + '.js', + ]; + + if (possiblePath.length) { + for (let i = 0; i < possiblePath.length; i++) { + const entry = possiblePath[i]; + if (fs.existsSync(entry)) { + const resolved = path.relative(root, entry.replace(/\.ts$/, '.js')); + + if (!resolved.startsWith('.')) { + return './' + resolved; + } + + return resolved; + } + } + } + } + + return null; +} + +function replace(filePath, outFilePath, options) { + const code = fs.readFileSync(filePath).toString(); + const newCode = code.replace( + /(import|export) (.+?) from ('[^\n']+'|"[^\n"]+");/gs, + (found, action, imported, from) => { + const importPath = from.slice(1, -1); + const resolvedPath = resolveImportPath(filePath, importPath, options); + + if (resolvedPath) { + console.log('\t', importPath, resolvedPath); + return `${action} ${imported} from '${resolvedPath.replaceAll( + '\\', + '/' + )}';`; + } + + return found; + } + ); + + if (code !== newCode) { + fs.writeFileSync(outFilePath, newCode); + } +} + +// Then, use it with a simple async for loop +async function run(srcDir, options = defaultOptions) { + const {sourceFileFilter = defaultSourceFileFilter} = options; + + for await (const entry of walk(srcDir)) { + if (sourceFileFilter(entry)) { + console.log(entry); + replace(entry, entry, options); + } + } +} + +const defaultSourceFileFilter = function (sourceFilePath) { + return ( + /\.(js|ts)$/.test(sourceFilePath) && !/node_modules/.test(sourceFilePath) + ); +}; + +const defaultModuleFilter = function (importedModule) { + return ( + !path.isAbsolute(importedModule) && + !importedModule.startsWith('@') && + !importedModule.endsWith('.js') + ); +}; + +const defaultOptions = { + sourceFileFilter: defaultSourceFileFilter, + moduleFilter: defaultModuleFilter, +}; + +run('./dist/esm', defaultOptions); diff --git a/package.json b/package.json index 2d10832..4d8aad1 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,13 @@ { "name": "@adobe/css-tools", - "version": "4.0.2", + "version": "4.1.0", "description": "CSS parser / stringifier", "main": "dist/umd/cssTools.js", "module": "dist/cjs/cssTools.js", + "exports": { + "import": "./dist/esm/index.js", + "require": "./dist/cjs/cssTools.js" + }, "types": "dist/cjs/index.d.ts", "files": [ "dist", @@ -30,8 +34,9 @@ "test": "jest", "lint": "gts lint", "clean": "gts clean", - "build": "npm run compile", - "compile": "NODE_ENV=prod webpack --mode production", + "build": "npm run compile && npm run buildesm", + "buildesm": "tsc --project tsconfig.esm.json && node ./add_esm_import_extensions.mjs", + "compile": "set NODE_ENV=prod & webpack --mode production", "fix": "gts fix", "prepare": "npm run build", "pretest": "npm run build", diff --git a/tsconfig.esm.json b/tsconfig.esm.json new file mode 100644 index 0000000..f387b59 --- /dev/null +++ b/tsconfig.esm.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "node", + "rootDir": "src", + "outDir": "dist/esm", + }, + "include": [ + "src/**/*.ts" + ] +}