diff --git a/.gitignore b/.gitignore index 2700b7a3..38443a48 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ dist-ssr .npmrc build +presets +transforms diff --git a/cssTransform.js b/cssTransform.js new file mode 100644 index 00000000..1895471d --- /dev/null +++ b/cssTransform.js @@ -0,0 +1,76 @@ +'use strict'; + +Object.defineProperty(exports, '__esModule', { value: true }); + +require('path'); +require('camelcase'); + +function getRelativeFilename(filename) { + return filename.split(process.cwd())[1]; +} +function processCss(src, filename) { + if (filename.endsWith('.module.css')) { + return processCSSModules(src, filename); + } + const relativeFilename = getRelativeFilename(filename); + // Transform to a javascript module that load a tag to the page. + return `const relativeCssPath = "${relativeFilename}"; + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.href = relativeCssPath; + document.body.appendChild(link); + + module.exports = JSON.stringify(relativeCssPath);`; +} +// TODO: MEDIUM PRIORITY To research about getCacheKey +// Reference: +// - https://jestjs.io/docs/code-transformation#writing-custom-transformers +// - https://github.com/swc-project/jest/blob/17cf883b46c050a485975d8ce96a05277cf6032f/index.ts#L37-L52 +// const cacheKeyFunction = createCacheKey(); +// export function getCacheKey(src: string, filename: string, ...rest): string { +// const baseCacheKey = cacheKeyFunction(src, filename, ...rest); +// return crypto.createHash('md5').update(baseCacheKey).digest('hex'); +// } +// We cannot create async transformer if we are using CommonJS +// ( Reference: https://github.com/facebook/jest/issues/11081#issuecomment-791259034 +// https://github.com/facebook/jest/issues/11458 +// Also, there is a inconsistency in jest docs about should `process` be required +// https://jestjs.io/docs/configuration#transform-objectstring-pathtotransformer--pathtotransformer-object +// A transformer must be an object with at least a process function +// https://jestjs.io/docs/code-transformation#writing-custom-transformers +// As can be seen, only process or processAsync is mandatory to implement) +// We can use that if we opt-in to ESM. But it's not common use case right now (2022) +// So our approach is making CSS Modules a "CSS-in-JS" solution. +// CSS Modules will be compiled at run time then inject to the document.body +// One notable note is that `postcss-modules` is an async postcss plugin +// so we need to use `postcss-modules.sync`, with function `sync()` +function processCSSModules(src, filename) { + return ` +const postcss = require('postcss'); +const CSSModulesSync = require('postcss-modules-sync').default; +const cssSrc = ${JSON.stringify(src)}; + +let exportedTokens = {}; + +const result = postcss( + CSSModulesSync({ + getJSON: (tokens) => { + exportedTokens = tokens; + }, + }), +) +.process(cssSrc, { from: ${JSON.stringify(filename)} }) + +const style = document.createElement('style'); +style.type = 'text/css'; +style.appendChild(document.createTextNode(result.css)); +document.body.appendChild(style); + +module.exports = exportedTokens`; +} + +function process$1(src, filename) { + return processCss(src, filename); +} + +exports.process = process$1; diff --git a/examples/vite-react/jest.config.js b/examples/vite-react/jest.config.js index c88ceb95..dd4ddfe3 100644 --- a/examples/vite-react/jest.config.js +++ b/examples/vite-react/jest.config.js @@ -11,9 +11,9 @@ module.exports = { modulePaths: ['/src'], transform: { '^.+\\.(ts|js|tsx|jsx)$': '@swc/jest', - '^.+\\.css$': '/config/jest/cssTransform.js', + '^.+\\.css$': 'jest-preview/transforms/css', '^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)': - '/config/jest/fileTransform.js', + 'jest-preview/transforms/file', }, transformIgnorePatterns: [ '[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$', diff --git a/examples/vite-react/package.json b/examples/vite-react/package.json index 77b14e52..d205071b 100644 --- a/examples/vite-react/package.json +++ b/examples/vite-react/package.json @@ -8,6 +8,7 @@ "preview": "vite preview", "jest-preview": "jest-preview", "test": "NODE_ENV=test jest --watch", + "test:nc": "npm run test -- --no-cache", "test:debug": "npm-run-all -p test jest-preview" }, "dependencies": { diff --git a/package.json b/package.json index 7f50197f..20d5715d 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ }, "files": [ "dist", - "server" + "server", + "transforms" ], "scripts": { "dev": "vite", diff --git a/src/preconfig/cssTransform.ts b/src/preconfigTransform/css.ts similarity index 55% rename from src/preconfig/cssTransform.ts rename to src/preconfigTransform/css.ts index 55194ede..8bb1edee 100644 --- a/src/preconfig/cssTransform.ts +++ b/src/preconfigTransform/css.ts @@ -2,6 +2,7 @@ import { processCss } from '../transform'; -export function process(src: string, filename: string) { +function process(src: string, filename: string) { return processCss(src, filename); } +export default { process }; diff --git a/src/preconfigTransform/file.ts b/src/preconfigTransform/file.ts new file mode 100644 index 00000000..236ea502 --- /dev/null +++ b/src/preconfigTransform/file.ts @@ -0,0 +1,8 @@ +'use strict'; + +import { processFile } from '../transform'; + +function process(src: string, filename: string) { + return processFile(src, filename); +} +export default { process }; diff --git a/src/preconfigTransform/fileCRA.ts b/src/preconfigTransform/fileCRA.ts new file mode 100644 index 00000000..1da04089 --- /dev/null +++ b/src/preconfigTransform/fileCRA.ts @@ -0,0 +1,8 @@ +'use strict'; + +import { processFileCRA } from '../transform'; + +function process(src: string, filename: string) { + return processFileCRA(src, filename); +} +export default { process };