diff --git a/packages/module-utils/README.md b/packages/module-utils/README.md index b9afa99e1..b864ba3ad 100644 --- a/packages/module-utils/README.md +++ b/packages/module-utils/README.md @@ -8,9 +8,9 @@ Creates a factory for creating multiple Stylable modules in a shared context, used by the webpack plugin. -### `generateModuleSource` +### `generateStylableJSModuleSource` -Creates a single Stylable module source code, used by the node integration. +Creates a single Stylable module source code. ## License Copyright (c) 2017 Wix.com Ltd. All Rights Reserved. Use of this source code is governed by an [MIT license](./LICENSE). diff --git a/packages/module-utils/src/index.ts b/packages/module-utils/src/index.ts index c437761bf..98b1bb175 100644 --- a/packages/module-utils/src/index.ts +++ b/packages/module-utils/src/index.ts @@ -1,4 +1,7 @@ +/**@deprecated */ export { createModuleSource, generateModuleSource } from './module-source'; + export { Options, stylableModuleFactory } from './module-factory'; export { generateDTSContent } from './generate-dts'; export { generateDTSSourceMap } from './generate-dts-sourcemaps'; +export { generateStylableJSModuleSource } from './stylable-js-module-source'; diff --git a/packages/module-utils/src/module-factory.ts b/packages/module-utils/src/module-factory.ts index 33940750e..fd7e2ed0b 100644 --- a/packages/module-utils/src/module-factory.ts +++ b/packages/module-utils/src/module-factory.ts @@ -1,43 +1,46 @@ import { Stylable, StylableConfig } from '@stylable/core'; -import { generateModuleSource } from './module-source'; +import { generateStylableJSModuleSource } from './stylable-js-module-source'; export interface Options { + injectCSS: boolean; + staticImports: string[]; runtimePath: string; runtimeStylesheetId: 'module' | 'namespace'; - injectCSS: boolean; + moduleType: 'esm' | 'cjs'; + runtimeId: string; + /**@deprecated not in use */ renderableOnly: boolean; - staticImports: string[]; } export function stylableModuleFactory( stylableOptions: StylableConfig, { - runtimePath = '@stylable/runtime', + runtimePath, runtimeStylesheetId = 'module', injectCSS = true, - renderableOnly = false, staticImports = [], + moduleType = 'cjs', + runtimeId = '0', }: Partial = {} ) { const stylable = new Stylable(stylableOptions); return function stylableToModule(source: string, path: string) { - const meta = stylable.analyze(path, source); - const res = stylable.transform(meta); - return generateModuleSource( - res, - runtimeStylesheetId === 'module' ? 'module.id' : res.meta.namespace, - [ - ...staticImports.map((request) => `import ${JSON.stringify(request)}`), - `const runtime = require(${JSON.stringify(runtimePath)})`, - ], - `runtime.$`, - `runtime.create`, - `runtime.createRenderable`, - injectCSS ? JSON.stringify(res.meta.targetAst!.toString()) : '""', - '-1', // ToDo: calc depth for node as well - 'module.exports', - '' /* afterModule */, - renderableOnly + const { meta, exports } = stylable.transform(stylable.analyze(path, source)); + return generateStylableJSModuleSource( + { + moduleType: moduleType, + imports: staticImports.map((from) => ({ from })), + jsExports: exports, + namespace: meta.namespace, + runtimeRequest: runtimePath, + varType: 'var', + }, + { + id: runtimeStylesheetId === 'module' ? undefined : meta.namespace, + css: injectCSS ? meta.targetAst!.toString() : '', + depth: -1, + runtimeId, + } ); }; } diff --git a/packages/module-utils/src/module-source.ts b/packages/module-utils/src/module-source.ts index bc0287743..8bbb664ca 100644 --- a/packages/module-utils/src/module-source.ts +++ b/packages/module-utils/src/module-source.ts @@ -1,5 +1,8 @@ import type { StylableResults } from '@stylable/core'; +/** + * @deprecated use generateStylableJSModuleSource + */ export function generateModuleSource( stylableResult: StylableResults, moduleId: string, @@ -32,6 +35,9 @@ ${exportsArgument} = ${createFunction}( `; } +/** + * @deprecated use generateStylableJSModuleSource + */ export function createModuleSource( stylableResult: StylableResults, moduleFormat = 'cjs', diff --git a/packages/module-utils/src/stylable-js-module-source.ts b/packages/module-utils/src/stylable-js-module-source.ts new file mode 100644 index 000000000..9884bcb6f --- /dev/null +++ b/packages/module-utils/src/stylable-js-module-source.ts @@ -0,0 +1,148 @@ +import type { StylableExports } from '@stylable/core/dist/index-internal'; + +interface InjectCSSOptions { + /** + * omitting the id will fallback to module.id/import.meta.url + */ + id?: string | undefined; + /** + * css string to inject + */ + css: string; + /** + * calculated style depth + */ + depth: number | string; + /** + * use code to get the depth + */ + depthCode?: string; + /** + * reconciliation will happen only for style with the same runtimeId + */ + runtimeId: string; +} + +interface ModuleOptions { + /** + * module namespace + */ + namespace: string; + /** + * static imports for the module + */ + imports?: Array<{ from: string }>; + /** + * Stylable transforms exports + */ + jsExports: StylableExports | { [K in keyof StylableExports]: string }; + /** + * the request of the module runtime api e.g @stylable/runtime + */ + runtimeRequest?: string; + /** + * target module format + */ + moduleType: 'esm' | 'cjs'; + /** + * es3 compat mode + */ + varType?: 'const' | 'var'; + /** + * inject code immediately after imports + */ + header?: string; + /** + * inject code after the entire module code + */ + footer?: string; +} + +export function generateStylableJSModuleSource( + moduleOptions: ModuleOptions, + injectOptions?: InjectCSSOptions +) { + const { + namespace, + imports = [], + jsExports, + moduleType, + runtimeRequest, + varType = 'const', + header = '', + footer = '', + } = moduleOptions; + + const { classes, keyframes, layers, stVars, vars } = jsExports; + const exportKind = moduleType === 'esm' ? `export ${varType} ` : 'module.exports.'; + return ` +${imports.map(moduleRequest(moduleType)).join('\n')} +${runtimeImport(moduleType, runtimeRequest, injectOptions)} + +${header} + +${varType} _namespace_ = ${JSON.stringify(namespace)}; +${varType} _style_ = /*#__PURE__*/ classesRuntime.bind(null, _namespace_); + +${exportKind}cssStates = /*#__PURE__*/ statesRuntime.bind(null, _namespace_); +${exportKind}style = /*#__PURE__*/ _style_; +${exportKind}st = /*#__PURE__*/ _style_; + +${exportKind}namespace = _namespace_; +${exportKind}classes = ${JSON.stringify(classes)}; +${exportKind}keyframes = ${JSON.stringify(keyframes)}; +${exportKind}layers = ${JSON.stringify(layers)}; +${exportKind}stVars = ${JSON.stringify(stVars)}; +${exportKind}vars = ${JSON.stringify(vars)}; + +${runtimeExecuteInject(moduleType, injectOptions)} + +${footer} +`; +} + +function moduleRequest(moduleType: 'esm' | 'cjs') { + return (moduleRequest: { from: string }) => { + const request = JSON.stringify(moduleRequest.from); + return moduleType === 'esm' ? `import ${request};` : `require(${request});`; + }; +} + +function runtimeImport( + moduleType: 'esm' | 'cjs', + runtimeRequest: string | undefined, + injectOptions: InjectCSSOptions | undefined +) { + const importInjectCSS = injectOptions?.css ? `, injectCSS` : ''; + const request = JSON.stringify( + runtimeRequest ?? + // TODO: we use direct requests here since we don't know how this will be resolved + (moduleType === 'esm' + ? '@stylable/runtime/esm/runtime' + : '@stylable/runtime/dist/runtime') + ); + return moduleType === 'esm' + ? `import { classesRuntime, statesRuntime${importInjectCSS} } from ${request};` + : `const { classesRuntime, statesRuntime${importInjectCSS} } = require(${request});`; +} + +function runtimeExecuteInject( + moduleType: 'esm' | 'cjs', + injectOptions: InjectCSSOptions | undefined +) { + if (!injectOptions?.css) { + return ''; + } + const { id, css, depthCode, depth, runtimeId } = injectOptions; + + let out = 'injectCSS('; + out += id ? JSON.stringify(id) : moduleType === 'esm' ? 'import.meta.url' : 'module.id'; + out += ', '; + out += JSON.stringify(css); + out += ', '; + out += depthCode || JSON.stringify(depth) || '-1'; + out += ', '; + out += JSON.stringify(runtimeId); + out += ');'; + return out; +} diff --git a/packages/module-utils/test/index.spec.ts b/packages/module-utils/test/index.spec.ts index f353ca428..a80c48677 100644 --- a/packages/module-utils/test/index.spec.ts +++ b/packages/module-utils/test/index.spec.ts @@ -23,7 +23,7 @@ describe('Module Factory', () => { const testFile = '/entry.st.css'; const { fs, factory, evalStylableModule } = moduleFactoryTestKit( { - [testFile]: '.root {}', + [testFile]: '.root {background: red}', }, { injectCSS: false } ); @@ -33,11 +33,13 @@ describe('Module Factory', () => { const exports = evalStylableModule(moduleSource, testFile); expect(exports).to.deep.include({ - $css: '', + namespace: 'entry', classes: { root: 'entry__root', }, }); + expect(moduleSource).to.not.match(/background/); + expect(moduleSource).to.not.match(/injectCSS/); }); it('should create a module with cross file use', () => { @@ -84,8 +86,8 @@ describe('Module Factory', () => { const moduleSource = factory(fs.readFileSync(testFile, 'utf8'), testFile); - const exports = evalStylableModule(moduleSource, testFile); - expect(Object.keys(exports as {}).sort()).to.eql( + const exports = evalStylableModule<{}>(moduleSource, testFile); + expect(Object.keys(exports).sort()).to.eql( [ 'namespace', 'classes', @@ -96,9 +98,6 @@ describe('Module Factory', () => { 'cssStates', 'style', 'st', - '$id', - '$depth', - '$css', ].sort() ); }); diff --git a/packages/module-utils/test/test-kit.ts b/packages/module-utils/test/test-kit.ts index 00ceab098..c3cae8ea6 100644 --- a/packages/module-utils/test/test-kit.ts +++ b/packages/module-utils/test/test-kit.ts @@ -1,4 +1,4 @@ -import { create } from '@stylable/runtime'; +import * as runtime from '@stylable/runtime'; import { stylableModuleFactory, Options } from '@stylable/module-utils'; import type { IDirectoryContents } from '@file-services/types'; import { createMemoryFs } from '@file-services/memory'; @@ -23,8 +23,8 @@ function evalModule(id: string, source: string, requireModule: (s: string) => an */ export function evalStylableModule(source: string, fullPath: string): T { return evalModule(fullPath, source, (id) => { - if (id === '@stylable/runtime') { - return { create }; + if (id === '@stylable/runtime' || id === '@stylable/runtime/dist/runtime') { + return runtime; } throw new Error(`Could not find module: ${id}`); }) as T; diff --git a/packages/node/test/require-hook.spec.ts b/packages/node/test/require-hook.spec.ts index 2ddf98578..0670c8d0b 100644 --- a/packages/node/test/require-hook.spec.ts +++ b/packages/node/test/require-hook.spec.ts @@ -62,6 +62,7 @@ describe('require hook', () => { it('should ignoreJSModules', () => { attachHook({ ignoreJSModules: true }); const m = require(join(fixturesPath, 'has-js.st.css')); - expect(m.$id).to.contain('has-js.st.css'); + expect(m.test).equal(undefined); + expect(m.namespace).to.match(/^hasjs/); }); }); diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index b01e4c6e7..72203ba0d 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -15,3 +15,4 @@ export { RuntimeStVar, } from './types'; export { DOMListRenderer, createDOMListRenderer } from './keyed-list-renderer'; +export * from './runtime'; diff --git a/packages/runtime/src/inject-styles.ts b/packages/runtime/src/inject-styles.ts index 7b18ff5c6..7ca7756d7 100644 --- a/packages/runtime/src/inject-styles.ts +++ b/packages/runtime/src/inject-styles.ts @@ -44,4 +44,5 @@ export function injectStyles(host: Host) { } } host.sti = stylableRuntime; + return host; } diff --git a/packages/runtime/src/runtime.ts b/packages/runtime/src/runtime.ts index ce20c109e..7ef84952b 100644 --- a/packages/runtime/src/runtime.ts +++ b/packages/runtime/src/runtime.ts @@ -1,5 +1,6 @@ /* eslint-disable no-var */ import type { Host, StateMap, StateValue } from './types'; +import { injectStyles } from './inject-styles'; export function stylesheet(host?: Host) { host = host || {}; @@ -87,3 +88,7 @@ export function stylesheet(host?: Host) { host.stc = cssStates; return host as Required>; } + +const { sti } = injectStyles({}); +const { stc, sts } = stylesheet({}); +export { sti as injectCSS, stc as statesRuntime, sts as classesRuntime };