From 6166aa6a184d79295fa4860e9323efd2e5690507 Mon Sep 17 00:00:00 2001 From: Anton Evzhakov Date: Thu, 5 Oct 2023 14:44:51 +0300 Subject: [PATCH] feat(esbuild): former @linaria/esbuild (#12) * feat(esbuild): former @linaria/esbuild * fix(esbuild): imports and plugin name --- .changeset/weak-owls-rule.md | 7 ++ packages/esbuild/.eslintrc.js | 3 + packages/esbuild/babel.config.js | 3 + packages/esbuild/jest.config.js | 18 +++ packages/esbuild/package.json | 45 ++++++++ packages/esbuild/src/index.ts | 157 ++++++++++++++++++++++++++ packages/esbuild/tsconfig.eslint.json | 4 + packages/esbuild/tsconfig.json | 13 +++ packages/esbuild/tsconfig.lib.json | 8 ++ packages/esbuild/tsconfig.spec.json | 7 ++ pnpm-lock.yaml | 28 +++++ 11 files changed, 293 insertions(+) create mode 100644 .changeset/weak-owls-rule.md create mode 100644 packages/esbuild/.eslintrc.js create mode 100644 packages/esbuild/babel.config.js create mode 100644 packages/esbuild/jest.config.js create mode 100644 packages/esbuild/package.json create mode 100644 packages/esbuild/src/index.ts create mode 100644 packages/esbuild/tsconfig.eslint.json create mode 100644 packages/esbuild/tsconfig.json create mode 100644 packages/esbuild/tsconfig.lib.json create mode 100644 packages/esbuild/tsconfig.spec.json diff --git a/.changeset/weak-owls-rule.md b/.changeset/weak-owls-rule.md new file mode 100644 index 00000000..b8b0a841 --- /dev/null +++ b/.changeset/weak-owls-rule.md @@ -0,0 +1,7 @@ +--- +'@wyw-in-js/esbuild': patch +'@wyw-in-js/vite': patch +'wyw-in-js': patch +--- + +Plugin for esbuild. diff --git a/packages/esbuild/.eslintrc.js b/packages/esbuild/.eslintrc.js new file mode 100644 index 00000000..1ad149b8 --- /dev/null +++ b/packages/esbuild/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ['@wyw-in-js/eslint-config/library'], +}; diff --git a/packages/esbuild/babel.config.js b/packages/esbuild/babel.config.js new file mode 100644 index 00000000..dda52988 --- /dev/null +++ b/packages/esbuild/babel.config.js @@ -0,0 +1,3 @@ +const config = require('@wyw-in-js/babel-config'); + +module.exports = config; diff --git a/packages/esbuild/jest.config.js b/packages/esbuild/jest.config.js new file mode 100644 index 00000000..afeabf38 --- /dev/null +++ b/packages/esbuild/jest.config.js @@ -0,0 +1,18 @@ +// @ts-check + +/** + * @type {import('@jest/types').Config.InitialOptions} + */ +module.exports = { + displayName: 'webpack-loader', + preset: '@wyw-in-js/jest-preset', + transform: { + '^.+\\.ts$': [ + 'ts-jest', + { + tsconfig: '/tsconfig.spec.json', + isolatedModules: true, + }, + ], + }, +}; diff --git a/packages/esbuild/package.json b/packages/esbuild/package.json new file mode 100644 index 00000000..38cb9185 --- /dev/null +++ b/packages/esbuild/package.json @@ -0,0 +1,45 @@ +{ + "name": "@wyw-in-js/esbuild", + "version": "0.1.0", + "dependencies": { + "@wyw-in-js/shared": "workspace:*", + "@wyw-in-js/transform": "workspace:*" + }, + "devDependencies": { + "@types/node": "^16.18.55", + "@wyw-in-js/babel-config": "workspace:*", + "@wyw-in-js/eslint-config": "workspace:*", + "@wyw-in-js/jest-preset": "workspace:*", + "@wyw-in-js/ts-config": "workspace:*", + "esbuild": "^0.15.16" + }, + "engines": { + "node": ">=16.0.0" + }, + "files": [ + "esm/", + "lib/", + "types/" + ], + "license": "MIT", + "exports": { + "import": "./esm/index.js", + "require": "./lib/index.js", + "types": "./types/index.d.ts" + }, + "main": "lib/index.js", + "module": "esm/index.js", + "peerDependencies": { + "esbuild": ">=0.12.0" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build:esm": "babel src --out-dir esm --extensions '.js,.jsx,.ts,.tsx' --source-maps --delete-dir-on-start", + "build:lib": "cross-env NODE_ENV=legacy babel src --out-dir lib --extensions '.js,.jsx,.ts,.tsx' --source-maps --delete-dir-on-start", + "build:types": "tsc --project ./tsconfig.lib.json --baseUrl . --rootDir ./src", + "lint": "eslint --ext .js,.ts ." + }, + "types": "types/index.d.ts" +} diff --git a/packages/esbuild/src/index.ts b/packages/esbuild/src/index.ts new file mode 100644 index 00000000..e1872b1e --- /dev/null +++ b/packages/esbuild/src/index.ts @@ -0,0 +1,157 @@ +/** + * This file contains an esbuild loader for wyw-in-js. + * It uses the transform.ts function to generate class names from source code, + * returns transformed code without template literals and attaches generated source maps + */ + +import { readFileSync } from 'fs'; +import { basename, dirname, isAbsolute, join, parse, posix } from 'path'; + +import type { Plugin, TransformOptions, Loader } from 'esbuild'; +import { transformSync } from 'esbuild'; + +import type { PluginOptions, Preprocessor } from '@wyw-in-js/transform'; +import { + slugify, + transform, + TransformCacheCollection, +} from '@wyw-in-js/transform'; + +type EsbuildPluginOptions = { + esbuildOptions?: TransformOptions; + preprocessor?: Preprocessor; + sourceMap?: boolean; +} & Partial; + +const nodeModulesRegex = /^(?:.*[\\/])?node_modules(?:[\\/].*)?$/; + +export default function wywInJS({ + sourceMap, + preprocessor, + esbuildOptions, + ...rest +}: EsbuildPluginOptions = {}): Plugin { + let options = esbuildOptions; + const cache = new TransformCacheCollection(); + return { + name: 'wyw-in-js', + setup(build) { + const cssLookup = new Map(); + + const asyncResolve = async ( + token: string, + importer: string + ): Promise => { + const context = isAbsolute(importer) + ? dirname(importer) + : join(process.cwd(), dirname(importer)); + + const result = await build.resolve(token, { + resolveDir: context, + kind: 'import-statement', + }); + + if (result.errors.length > 0) { + throw new Error(`Cannot resolve ${token}`); + } + + return result.path.replace(/\\/g, posix.sep); + }; + + build.onResolve({ filter: /\.linaria\.css$/ }, (args) => { + return { + namespace: 'linaria', + path: args.path, + }; + }); + + build.onLoad({ filter: /.*/, namespace: 'linaria' }, (args) => { + return { + contents: cssLookup.get(args.path), + loader: 'css', + resolveDir: basename(args.path), + }; + }); + + build.onLoad({ filter: /\.(js|jsx|ts|tsx)$/ }, async (args) => { + const rawCode = readFileSync(args.path, 'utf8'); + const { ext, name: filename } = parse(args.path); + const loader = ext.replace(/^\./, '') as Loader; + + if (nodeModulesRegex.test(args.path)) { + return { + loader, + contents: rawCode, + }; + } + + if (!options) { + options = {}; + if ('jsxFactory' in build.initialOptions) { + options.jsxFactory = build.initialOptions.jsxFactory; + } + if ('jsxFragment' in build.initialOptions) { + options.jsxFragment = build.initialOptions.jsxFragment; + } + } + + const transformed = transformSync(rawCode, { + ...options, + sourcefile: args.path, + sourcemap: sourceMap, + loader, + }); + let { code } = transformed; + + if (sourceMap) { + const esbuildMap = Buffer.from(transformed.map).toString('base64'); + code += `/*# sourceMappingURL=data:application/json;base64,${esbuildMap}*/`; + } + + const transformServices = { + options: { + filename: args.path, + root: process.cwd(), + preprocessor, + pluginOptions: rest, + }, + cache, + }; + + const result = await transform(transformServices, code, asyncResolve); + + if (!result.cssText) { + return { + contents: code, + loader, + resolveDir: dirname(args.path), + }; + } + + let { cssText } = result; + + const slug = slugify(cssText); + const cssFilename = `${filename}_${slug}.linaria.css`; + + let contents = `import ${JSON.stringify(cssFilename)}; ${result.code}`; + + if (sourceMap && result.cssSourceMapText) { + const map = Buffer.from(result.cssSourceMapText).toString('base64'); + cssText += `/*# sourceMappingURL=data:application/json;base64,${map}*/`; + const linariaMap = Buffer.from( + JSON.stringify(result.sourceMap) + ).toString('base64'); + contents += `/*# sourceMappingURL=data:application/json;base64,${linariaMap}*/`; + } + + cssLookup.set(cssFilename, cssText); + + return { + contents, + loader, + resolveDir: dirname(args.path), + }; + }); + }, + }; +} diff --git a/packages/esbuild/tsconfig.eslint.json b/packages/esbuild/tsconfig.eslint.json new file mode 100644 index 00000000..b139ef3a --- /dev/null +++ b/packages/esbuild/tsconfig.eslint.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "include": ["./src/**/*.ts"] +} diff --git a/packages/esbuild/tsconfig.json b/packages/esbuild/tsconfig.json new file mode 100644 index 00000000..1053d7a8 --- /dev/null +++ b/packages/esbuild/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "@wyw-in-js/ts-config/node.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/packages/esbuild/tsconfig.lib.json b/packages/esbuild/tsconfig.lib.json new file mode 100644 index 00000000..177fdab8 --- /dev/null +++ b/packages/esbuild/tsconfig.lib.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./types" + }, + "exclude": ["**/__tests__/*"], + "include": ["./src/**/*.ts"] +} diff --git a/packages/esbuild/tsconfig.spec.json b/packages/esbuild/tsconfig.spec.json new file mode 100644 index 00000000..2ba71dfb --- /dev/null +++ b/packages/esbuild/tsconfig.spec.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true + }, + "include": ["**/__tests__/*"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a4dfdd3d..1ca9e788 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -218,6 +218,34 @@ importers: specifier: ^1.5.1 version: 1.5.1 + packages/esbuild: + dependencies: + '@wyw-in-js/shared': + specifier: workspace:* + version: link:../shared + '@wyw-in-js/transform': + specifier: workspace:* + version: link:../transform + devDependencies: + '@types/node': + specifier: ^16.18.55 + version: 16.18.55 + '@wyw-in-js/babel-config': + specifier: workspace:* + version: link:../../configs/babel + '@wyw-in-js/eslint-config': + specifier: workspace:* + version: link:../../configs/eslint + '@wyw-in-js/jest-preset': + specifier: workspace:* + version: link:../../configs/jest + '@wyw-in-js/ts-config': + specifier: workspace:* + version: link:../../configs/ts + esbuild: + specifier: ^0.15.16 + version: 0.15.18 + packages/processor-utils: dependencies: '@babel/generator':