Skip to content

Commit

Permalink
feat: new generateStylableJSModuleSource api (#2660)
Browse files Browse the repository at this point in the history
  • Loading branch information
barak007 authored Aug 11, 2022
1 parent 3f0347a commit ac7103b
Show file tree
Hide file tree
Showing 11 changed files with 202 additions and 35 deletions.
4 changes: 2 additions & 2 deletions packages/module-utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
3 changes: 3 additions & 0 deletions packages/module-utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -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';
47 changes: 25 additions & 22 deletions packages/module-utils/src/module-factory.ts
Original file line number Diff line number Diff line change
@@ -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<Options> = {}
) {
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,
}
);
};
}
6 changes: 6 additions & 0 deletions packages/module-utils/src/module-source.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import type { StylableResults } from '@stylable/core';

/**
* @deprecated use generateStylableJSModuleSource
*/
export function generateModuleSource(
stylableResult: StylableResults,
moduleId: string,
Expand Down Expand Up @@ -32,6 +35,9 @@ ${exportsArgument} = ${createFunction}(
`;
}

/**
* @deprecated use generateStylableJSModuleSource
*/
export function createModuleSource(
stylableResult: StylableResults,
moduleFormat = 'cjs',
Expand Down
148 changes: 148 additions & 0 deletions packages/module-utils/src/stylable-js-module-source.ts
Original file line number Diff line number Diff line change
@@ -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;
}
13 changes: 6 additions & 7 deletions packages/module-utils/test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
);
Expand All @@ -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', () => {
Expand Down Expand Up @@ -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',
Expand All @@ -96,9 +98,6 @@ describe('Module Factory', () => {
'cssStates',
'style',
'st',
'$id',
'$depth',
'$css',
].sort()
);
});
Expand Down
6 changes: 3 additions & 3 deletions packages/module-utils/test/test-kit.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -23,8 +23,8 @@ function evalModule(id: string, source: string, requireModule: (s: string) => an
*/
export function evalStylableModule<T = unknown>(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;
Expand Down
3 changes: 2 additions & 1 deletion packages/node/test/require-hook.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/);
});
});
1 change: 1 addition & 0 deletions packages/runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ export {
RuntimeStVar,
} from './types';
export { DOMListRenderer, createDOMListRenderer } from './keyed-list-renderer';
export * from './runtime';
1 change: 1 addition & 0 deletions packages/runtime/src/inject-styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,5 @@ export function injectStyles(host: Host) {
}
}
host.sti = stylableRuntime;
return host;
}
5 changes: 5 additions & 0 deletions packages/runtime/src/runtime.ts
Original file line number Diff line number Diff line change
@@ -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 || {};
Expand Down Expand Up @@ -87,3 +88,7 @@ export function stylesheet(host?: Host) {
host.stc = cssStates;
return host as Required<Pick<Host, 'stc' | 'sts'>>;
}

const { sti } = injectStyles({});
const { stc, sts } = stylesheet({});
export { sti as injectCSS, stc as statesRuntime, sts as classesRuntime };

0 comments on commit ac7103b

Please sign in to comment.