diff --git a/esbuild/build.js b/esbuild/build.js index cc3eac1..097b743 100755 --- a/esbuild/build.js +++ b/esbuild/build.js @@ -15,11 +15,8 @@ const common = { }; (async () => { - const runtime = await esbuild.build({ - ...common, + const runtimeCommon = { entryPoints: ['src/runtime/index.ts'], - outfile: 'build/runtime/index.js', - minify: true, loader: { '.svg': 'text', '.ttf': 'file', @@ -27,9 +24,24 @@ const common = { '.woff2': 'file', }, plugins: [inlineScss()], + }; + + const runtime = await esbuild.build({ + ...common, + ...runtimeCommon, + outfile: 'build/runtime/index-node.js', + minify: true, metafile: true, }); + esbuild.build({ + ...common, + ...runtimeCommon, + outfile: 'build/runtime/index.js', + external: ['katex'], + platform: 'neutral', + }); + esbuild.build({ ...common, entryPoints: ['src/react/index.ts'], @@ -38,11 +50,7 @@ const common = { external: ['react'], }); - esbuild.build({ - ...common, - entryPoints: ['src/plugin/index.ts'], - outfile: 'build/plugin/index.js', - platform: 'node', + const pluginCommon = { external: ['markdown-it', 'node:*'], define: { PACKAGE: JSON.stringify(require('../package.json').name), @@ -52,5 +60,22 @@ const common = { .map((file) => file.replace(/^runtime\//, '')), ), }, + }; + + esbuild.build({ + ...common, + ...pluginCommon, + entryPoints: ['src/plugin/index.ts'], + outfile: 'build/plugin/index.js', + platform: 'neutral', + external: [...pluginCommon.external, 'katex'], + }); + + esbuild.build({ + ...common, + ...pluginCommon, + entryPoints: ['src/plugin/index-node.ts'], + outfile: 'build/plugin/index-node.js', + platform: 'node', }); })(); diff --git a/package.json b/package.json index bbb3969..7c592ae 100644 --- a/package.json +++ b/package.json @@ -5,13 +5,35 @@ "main": "build/plugin/index.js", "types": "build/plugin/index.d.ts", "exports": { - ".": "./build/plugin/index.js", - "./plugin": "./build/plugin/index.js", - "./runtime": "./build/runtime/index.js", - "./runtime/styles": "./build/runtime/index.css", + ".": { + "node": "./build/plugin/index-node.js", + "default": "./build/plugin/index.js" + }, + "./plugin": { + "node": "./build/plugin/index-node.js", + "default": "./build/plugin/index.js" + }, + "./runtime": { + "node": "./build/runtime/index-node.js", + "default": "./build/runtime/index.js" + }, + "./runtime/styles": "./build/runtime/index-node.css", "./react": "./build/react/index.js", "./hooks": "./build/react/index.js" }, + "typesVersions": { + "*": { + "index.d.ts": [ + "./build/plugin/index.d.ts" + ], + "plugin": [ + "./build/plugin/index.d.ts" + ], + "runtime": [ + "./build/runtime/index.d.ts" + ] + } + }, "scripts": { "build": "run-p build:*", "build:js": "./esbuild/build.js", diff --git a/src/plugin/index-node.ts b/src/plugin/index-node.ts new file mode 100644 index 0000000..a1717ae --- /dev/null +++ b/src/plugin/index-node.ts @@ -0,0 +1 @@ +export {transform} from './transform-node'; diff --git a/src/plugin/transform-node.ts b/src/plugin/transform-node.ts new file mode 100644 index 0000000..d4c636b --- /dev/null +++ b/src/plugin/transform-node.ts @@ -0,0 +1,32 @@ +import type {PluginOptions, RuntimeObj} from './types'; + +import {dirname, join} from 'node:path'; +import {copyFileSync, mkdirSync} from 'node:fs'; + +import {transform as baseTransform} from './transform'; + +function copy(from: string, to: string) { + mkdirSync(dirname(to), {recursive: true}); + copyFileSync(from, to); +} + +export function onBundle(env: {bundled: Set}, output: string, runtime: RuntimeObj) { + env.bundled.add(PACKAGE); + + const root = dirname(require.resolve(join(PACKAGE, 'runtime'))); + + RUNTIME.forEach((file) => { + switch (true) { + case file === 'index.js': + return copy(join(root, file), join(output, runtime.script)); + case file === 'index.css': + return copy(join(root, file), join(output, runtime.style)); + default: + return copy(join(root, file), join(output, dirname(runtime.script), file)); + } + }); +} + +export const transform = (options?: Partial) => { + return baseTransform(options); +}; diff --git a/src/plugin/transform.ts b/src/plugin/transform.ts index c309de7..0ca15c5 100644 --- a/src/plugin/transform.ts +++ b/src/plugin/transform.ts @@ -5,10 +5,12 @@ import type { import type {RuleBlock} from 'markdown-it/lib/parser_block'; import type {RuleInline} from 'markdown-it/lib/parser_inline'; import type {RenderRule} from 'markdown-it/lib/renderer'; -import type {KatexOptions} from 'katex'; -import type MarkdownIt from 'markdown-it'; +import type {NormalizedPluginOptions, PluginOptions} from './types'; -import {copy, dynrequire, hidden} from './utils'; +import MarkdownIt from 'markdown-it'; +import katex from 'katex'; + +import {hidden} from './utils'; // Assumes that there is a "$" at state.src[pos] function isValidDelim(state: Parameters[0], pos: number) { @@ -178,8 +180,9 @@ const registerTransforms = ( { runtime, bundle, + onBundle, output, - }: Pick & { + }: Pick & { output: string; }, ) => { @@ -200,26 +203,8 @@ const registerTransforms = ( env.meta.style = env.meta.style || []; env.meta.style.push(runtime.style); - if (bundle && !env.bundled.has(PACKAGE)) { - const {dirname, join} = dynrequire('node:path'); - - env.bundled.add(PACKAGE); - - const root = dirname(require.resolve(join(PACKAGE, 'runtime'))); - - RUNTIME.forEach((file) => { - switch (true) { - case file === 'index.js': - return copy(join(root, file), join(output, runtime.script)); - case file === 'index.css': - return copy(join(root, file), join(output, runtime.style)); - default: - return copy( - join(root, file), - join(output, dirname(runtime.script), file), - ); - } - }); + if (bundle && !env.bundled.has(PACKAGE) && onBundle) { + onBundle(env, output, runtime); } } @@ -233,32 +218,18 @@ const registerTransforms = ( }); }; -export type NormalizedPluginOptions = Omit & { - runtime: { - script: string; - style: string; - }; -}; - -export type PluginOptions = { - runtime: - | string - | { - script: string; - style: string; - }; - bundle: boolean; - validate: boolean; - classes: string; - katexOptions: KatexOptions; -}; - type InputOptions = MarkdownItPluginOpts & { destRoot: string; }; export function transform(options: Partial = {}) { - const {classes = 'yfm-latex', bundle = true, validate = true, katexOptions = {}} = options; + const { + classes = 'yfm-latex', + bundle = true, + validate = true, + katexOptions = {}, + onBundle, + } = options; if (bundle && typeof options.runtime === 'string') { throw new TypeError('Option `runtime` should be record when `bundle` is enabled.'); @@ -282,7 +253,7 @@ export function transform(options: Partial = {}) { }; if (validate) { - dynrequire('katex').renderToString(content, { + katex.renderToString(content, { ...options, throwOnError: false, }); @@ -299,6 +270,7 @@ export function transform(options: Partial = {}) { runtime, bundle, output, + onBundle, }); md.renderer.rules.math_inline = render('span', false); @@ -307,12 +279,12 @@ export function transform(options: Partial = {}) { Object.assign(plugin, { collect(input: string, {destRoot = '.'}: InputOptions) { - const MdIt = dynrequire('markdown-it'); - const md = new MdIt().use((md: MarkdownIt) => { + const md = new MarkdownIt().use((md) => { registerTransforms(md, { runtime, bundle, output: destRoot, + onBundle, }); }); diff --git a/src/plugin/types.ts b/src/plugin/types.ts new file mode 100644 index 0000000..32457cf --- /dev/null +++ b/src/plugin/types.ts @@ -0,0 +1,19 @@ +import type {KatexOptions} from 'katex'; + +export interface RuntimeObj { + script: string; + style: string; +} + +export type PluginOptions = { + runtime: string | RuntimeObj; + bundle: boolean; + validate: boolean; + classes: string; + katexOptions: KatexOptions; + onBundle?: (env: {bundled: Set}, output: string, runtime: RuntimeObj) => void; +}; + +export type NormalizedPluginOptions = Omit & { + runtime: RuntimeObj; +}; diff --git a/src/plugin/utils.ts b/src/plugin/utils.ts index fe2a0eb..a197fc1 100644 --- a/src/plugin/utils.ts +++ b/src/plugin/utils.ts @@ -12,20 +12,3 @@ export function hidden, F extends str return box as B & {[P in F]: V}; } - -export function copy(from: string, to: string) { - const {mkdirSync, copyFileSync} = dynrequire('node:fs'); - const {dirname} = dynrequire('node:path'); - - mkdirSync(dirname(to), {recursive: true}); - copyFileSync(from, to); -} - -/* - * Runtime require hidden for builders. - * Used for nodejs api - */ -export function dynrequire(module: string) { - // eslint-disable-next-line no-eval - return eval(`require('${module}')`); -}