Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new generateStylableJSModuleSource api #2660

Merged
merged 4 commits into from
Aug 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 };