poveste 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/node/build.js +0 -3
  2. package/dist/node/builtin-plugins/tailwind-tokens.d.ts +6 -1
  3. package/dist/node/builtin-plugins/tailwind-tokens.d.ts.map +1 -1
  4. package/dist/node/builtin-plugins/tailwind-tokens.js +169 -33
  5. package/dist/node/builtin-plugins/vanilla-support/MountStory.d.ts +3 -3
  6. package/dist/node/builtin-plugins/vanilla-support/RenderStory.d.ts +5 -5
  7. package/dist/node/collect/index.d.ts.map +1 -1
  8. package/dist/node/collect/index.js +1 -3
  9. package/dist/node/config.d.ts +2 -18
  10. package/dist/node/config.d.ts.map +1 -1
  11. package/dist/node/config.js +3 -0
  12. package/dist/node/dom/env.d.ts.map +1 -1
  13. package/dist/node/dom/env.js +10 -6
  14. package/dist/node/index.d.ts +0 -7
  15. package/dist/node/index.d.ts.map +1 -1
  16. package/dist/node/markdown.d.ts.map +1 -1
  17. package/dist/node/markdown.js +21 -3
  18. package/dist/node/stories.js +1 -1
  19. package/dist/node/style-isolation/entry-css-merger.d.ts.map +1 -1
  20. package/dist/node/style-isolation/entry-css-merger.js +8 -7
  21. package/dist/node/style-isolation/transforms.js +32 -8
  22. package/dist/node/vite.js +1 -1
  23. package/package.json +10 -12
  24. package/src/node/__tests__/dom-env.spec.ts +19 -1
  25. package/src/node/build.ts +2 -6
  26. package/src/node/builtin-plugins/tailwind-tokens.ts +190 -34
  27. package/src/node/collect/index.ts +1 -3
  28. package/src/node/config.ts +3 -0
  29. package/src/node/dom/env.ts +10 -6
  30. package/src/node/index.ts +0 -8
  31. package/src/node/markdown.ts +25 -3
  32. package/src/node/stories.ts +1 -1
  33. package/src/node/style-isolation/__tests__/entry-css-merger.spec.ts +37 -18
  34. package/src/node/style-isolation/__tests__/transforms.spec.ts +46 -8
  35. package/src/node/style-isolation/entry-css-merger.ts +10 -9
  36. package/src/node/style-isolation/transforms.ts +36 -7
  37. package/src/node/vite.ts +4 -4
  38. package/tsconfig.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import type { ServerMarkdownFile } from '@poveste/shared'
2
+ import type { Highlighter } from 'shiki'
2
3
  import type { Plugin as VitePlugin } from 'vite'
3
4
  import type { Context } from './context.js'
4
5
  import { kebabCase } from 'change-case'
@@ -28,14 +29,35 @@ function notifyMarkdownListChange() {
28
29
  }
29
30
  }
30
31
 
31
- export async function createMarkdownRenderer(ctx: Context) {
32
- const highlighter = await createHighlighter({
32
+ /**
33
+ * Shared between the markdown Vite plugin and the markdown file watcher, which
34
+ * both build a renderer — a `poveste build` reaches this three times. The
35
+ * themes and langs are constant, and each instance eagerly loads every bundled
36
+ * grammar, so the duplicates are pure waste.
37
+ */
38
+ let highlighterPromise: Promise<Highlighter> | undefined
39
+
40
+ function getHighlighter() {
41
+ highlighterPromise ??= createHighlighter({
33
42
  themes: ['github-dark'],
34
43
  langs: Object.keys(bundledLanguages), // not ideal but markdown-it does not provide async highlight
35
44
  })
36
45
 
46
+ return highlighterPromise
47
+ }
48
+
49
+ export async function createMarkdownRenderer(ctx: Context) {
50
+ const highlighter = await getHighlighter()
51
+
37
52
  const md = new MarkdownIt({
38
- highlight: (code, lang) => `<div class="ptw-relative ptw-not-prose __poveste-code __histoire-code"><div class="ptw-absolute ptw-top-0 ptw-right-0 ptw-text-xs ptw-text-white/40">${lang}</div>${highlighter.codeToHtml(code, { theme: 'github-dark', lang })}</div>`,
53
+ /*
54
+ * Unprefixed utilities: the `ptw-` prefix went away with Tailwind v4, and
55
+ * `not-prose` in particular is matched by the typography plugin's own
56
+ * `[class~="not-prose"]` selector, so a prefixed spelling silently opts the
57
+ * block back *into* prose. These classes are generated because main.pcss
58
+ * `@source`s this file — v4 auto-detection only scans the app package.
59
+ */
60
+ highlight: (code, lang) => `<div class="relative not-prose __poveste-code __histoire-code"><div class="absolute top-0 right-0 text-xs text-white/40">${lang}</div>${highlighter.codeToHtml(code, { theme: 'github-dark', lang })}</div>`,
39
61
  linkify: true,
40
62
  html: true,
41
63
  breaks: false,
@@ -76,7 +76,7 @@ export async function watchStories(newContext: Context) {
76
76
  watcher
77
77
  .on('add', (file) => {
78
78
  const storyFile = addStory(file)
79
- setTimeout(() => notifyStoryChange(storyFile), 100) // Delay in case file renaming fired Add event before Unlink event
79
+ setTimeout(notifyStoryChange, 100, storyFile) // Delay in case file renaming fired Add event before Unlink event
80
80
  })
81
81
  .on('unlink', (file) => {
82
82
  removeStory(file)
@@ -1,4 +1,4 @@
1
- import type { OutputBundle } from 'rollup'
1
+ import type { Rolldown } from 'vite'
2
2
  import { describe, expect, it } from 'vitest'
3
3
  import { entryCssMergerPlugin } from '../entry-css-merger.js'
4
4
  import { USER_CSS_MARK_END, USER_CSS_MARK_START } from '../vite-plugin.js'
@@ -30,25 +30,44 @@ function mark(css: string): string {
30
30
  return `${USER_CSS_MARK_START}\n${css}\n${USER_CSS_MARK_END}\n`
31
31
  }
32
32
 
33
+ // Rolldown ignores assignment to `bundle` in generateBundle, so merged CSS is
34
+ // added via this.emitFile. Capture those emits to assert on them.
35
+ function runMerger(plugin: any, bundle: Rolldown.OutputBundle) {
36
+ const emitted = new Map<string, any>()
37
+ const ctx = {
38
+ emitFile: (file: any) => {
39
+ emitted.set(file.fileName, file)
40
+ return file.fileName
41
+ },
42
+ }
43
+ ;(plugin.generateBundle as any).call(ctx, {}, bundle)
44
+ return emitted
45
+ }
46
+
33
47
  describe('entry-css-merger', () => {
34
48
  it('produces one CSS asset per entry chunk', async () => {
35
49
  const plugin = entryCssMergerPlugin()
36
- const bundle: OutputBundle = {
50
+ const bundle: Rolldown.OutputBundle = {
37
51
  'bundle-main.js': fakeChunk('bundle-main', ['route1.css', 'route2.css']),
38
52
  'bundle-sandbox.js': fakeChunk('bundle-sandbox', ['sandbox-route.css']),
39
53
  'route1.css': fakeAsset('route1.css', 'a {}'),
40
54
  'route2.css': fakeAsset('route2.css', 'b {}'),
41
55
  'sandbox-route.css': fakeAsset('sandbox-route.css', 'c {}'),
42
56
  }
43
- await (plugin.generateBundle as any).call({ emitFile: () => '' }, {}, bundle)
57
+ const emitted = runMerger(plugin, bundle)
44
58
 
45
- const cssAssets = Object.values(bundle).filter(a => a.type === 'asset' && a.fileName.endsWith('.css'))
59
+ const cssAssets = [...emitted.values()].filter(a => a.fileName.endsWith('.css'))
46
60
  expect(cssAssets).toHaveLength(2)
47
- const mainCss = cssAssets.find(a => a.fileName === 'bundle-main.css')
48
- const sandboxCss = cssAssets.find(a => a.fileName === 'bundle-sandbox.css')
61
+ const mainCss = emitted.get('bundle-main.css')
62
+ const sandboxCss = emitted.get('bundle-sandbox.css')
49
63
  expect(mainCss?.source).toContain('a {}')
50
64
  expect(mainCss?.source).toContain('b {}')
51
65
  expect(sandboxCss?.source).toContain('c {}')
66
+
67
+ // The per-chunk CSS assets are dropped from the bundle once merged.
68
+ expect(bundle['route1.css']).toBeUndefined()
69
+ expect(bundle['route2.css']).toBeUndefined()
70
+ expect(bundle['sandbox-route.css']).toBeUndefined()
52
71
  })
53
72
 
54
73
  it('wraps marked user CSS in @scope on the main entry when isolation is enabled', async () => {
@@ -57,14 +76,14 @@ describe('entry-css-merger', () => {
57
76
  scopeRoot: '.__poveste-render-story',
58
77
  mainEntryName: 'bundle-main',
59
78
  })
60
- const bundle: OutputBundle = {
79
+ const bundle: Rolldown.OutputBundle = {
61
80
  'bundle-main.js': fakeChunk('bundle-main', ['user.css', 'chrome.css']),
62
81
  'user.css': fakeAsset('user.css', mark('body { color: red }')),
63
82
  'chrome.css': fakeAsset('chrome.css', '@scope (.poveste-app-root) { .x {} }'),
64
83
  }
65
- await (plugin.generateBundle as any).call({ emitFile: () => '' }, {}, bundle)
84
+ const emitted = runMerger(plugin, bundle)
66
85
 
67
- const main = Object.values(bundle).find(a => a.type === 'asset' && a.fileName === 'bundle-main.css') as any
86
+ const main = emitted.get('bundle-main.css')
68
87
  expect(main.source).toContain('@scope (.__poveste-render-story)')
69
88
  expect(main.source).toContain('color: red')
70
89
  expect(main.source).toContain('@scope (.poveste-app-root)')
@@ -77,13 +96,13 @@ describe('entry-css-merger', () => {
77
96
  scopeRoot: '.__poveste-render-story',
78
97
  mainEntryName: 'bundle-main',
79
98
  })
80
- const bundle: OutputBundle = {
99
+ const bundle: Rolldown.OutputBundle = {
81
100
  'bundle-sandbox.js': fakeChunk('bundle-sandbox', ['user.css']),
82
101
  'user.css': fakeAsset('user.css', mark('body { color: red }')),
83
102
  }
84
- await (plugin.generateBundle as any).call({ emitFile: () => '' }, {}, bundle)
103
+ const emitted = runMerger(plugin, bundle)
85
104
 
86
- const sandbox = Object.values(bundle).find(a => a.type === 'asset' && a.fileName === 'bundle-sandbox.css') as any
105
+ const sandbox = emitted.get('bundle-sandbox.css')
87
106
  expect(sandbox.source).toContain('body')
88
107
  expect(sandbox.source).toContain('color: red')
89
108
  expect(sandbox.source).not.toContain('@scope')
@@ -95,14 +114,14 @@ describe('entry-css-merger', () => {
95
114
  const main = fakeChunk('bundle-main', ['app.css'], '', { imports: ['vendor.js'], isEntry: true })
96
115
  main.fileName = 'bundle-main.js'
97
116
  const vendor = { ...fakeChunk('vendor', ['vendor.css']), isEntry: false, fileName: 'vendor.js' }
98
- const bundle: OutputBundle = {
117
+ const bundle: Rolldown.OutputBundle = {
99
118
  'bundle-main.js': main,
100
119
  'vendor.js': vendor,
101
120
  'app.css': fakeAsset('app.css', '.app {}'),
102
121
  'vendor.css': fakeAsset('vendor.css', '.vendor {}'),
103
122
  }
104
- await (plugin.generateBundle as any).call({ emitFile: () => '' }, {}, bundle)
105
- const mainCss = Object.values(bundle).find(a => a.type === 'asset' && a.fileName === 'bundle-main.css') as any
123
+ const emitted = runMerger(plugin, bundle)
124
+ const mainCss = emitted.get('bundle-main.css')
106
125
  expect(mainCss.source).toContain('.app')
107
126
  expect(mainCss.source).toContain('.vendor')
108
127
  })
@@ -113,13 +132,13 @@ describe('entry-css-merger', () => {
113
132
  scopeRoot: '.__poveste-render-story',
114
133
  mainEntryName: 'bundle-main',
115
134
  })
116
- const bundle: OutputBundle = {
135
+ const bundle: Rolldown.OutputBundle = {
117
136
  'bundle-main.js': fakeChunk('bundle-main', ['user.css']),
118
137
  'user.css': fakeAsset('user.css', mark('body { color: red }')),
119
138
  }
120
- await (plugin.generateBundle as any).call({ emitFile: () => '' }, {}, bundle)
139
+ const emitted = runMerger(plugin, bundle)
121
140
 
122
- const main = Object.values(bundle).find(a => a.type === 'asset' && a.fileName === 'bundle-main.css') as any
141
+ const main = emitted.get('bundle-main.css')
123
142
  expect(main.source).not.toContain('@scope')
124
143
  expect(main.source).not.toContain(USER_CSS_MARK_START)
125
144
  })
@@ -1,21 +1,48 @@
1
1
  import { describe, expect, it } from 'vitest'
2
2
  import { isGlobalImport, wrapChromeCss, wrapUserCss } from '../transforms.js'
3
3
 
4
+ /** Selectors of every rule inside the wrap, so a test can assert on them whole. */
5
+ function selectorsIn(css: string): string[] {
6
+ return css.split('{')
7
+ .slice(0, -1)
8
+ .map(chunk => chunk.slice(chunk.lastIndexOf('}') + 1).trim())
9
+ .filter(selector => selector.length > 0 && !selector.startsWith('@'))
10
+ }
11
+
4
12
  describe('wrapUserCss', () => {
5
13
  it('wraps a single rule in @scope (.scope-root)', () => {
6
- const out = wrapUserCss('body { color: red }', { scopeRoot: '.__poveste-render-story' })
14
+ const out = wrapUserCss('main { color: red }', { scopeRoot: '.__poveste-render-story' })
7
15
  expect(out).toContain('@scope (.__poveste-render-story)')
8
- expect(out).toContain('body')
16
+ expect(out).toContain('main')
9
17
  expect(out).toContain('color: red')
10
18
  })
11
19
 
12
- it('rewrites :root → :scope inside the scope', () => {
13
- const out = wrapUserCss(':root { --color-primary: blue }', {
20
+ // Every root spelling has to land on :scope. A rule left as `html`/`body`
21
+ // targets an ancestor of the scoping root and can never match — silently,
22
+ // which is what made #116 survive this long. Asserting the whole selector
23
+ // rather than "contains :scope": the failure mode is a leftover `html`, and
24
+ // a substring check passes right over it.
25
+ it.each([
26
+ [':root', ':root { --brand: rebeccapurple }', ':scope'],
27
+ ['html', 'html { --brand: rebeccapurple }', ':scope'],
28
+ ['body', 'body { --brand: rebeccapurple }', ':scope'],
29
+ ['BODY', 'BODY { --brand: rebeccapurple }', ':scope'],
30
+ ['body .card', 'body .card { --brand: rebeccapurple }', ':scope .card'],
31
+ ['body.dark .card', 'body.dark .card { --brand: rebeccapurple }', ':scope.dark .card'],
32
+ ['.body-copy', '.body-copy { --brand: rebeccapurple }', '.body-copy'],
33
+ ['.page-html', '.page-html { --brand: rebeccapurple }', '.page-html'],
34
+ ])('compiles %s to a selector that can still match', (_input, css, expected) => {
35
+ const out = wrapUserCss(css, { scopeRoot: '.__poveste-render-story' })
36
+
37
+ expect(selectorsIn(out)).toEqual([expected])
38
+ })
39
+
40
+ it('rewrites root selectors nested in at-rules', () => {
41
+ const out = wrapUserCss('@media (min-width: 700px) { body { --brand: rebeccapurple } }', {
14
42
  scopeRoot: '.__poveste-render-story',
15
43
  })
16
- expect(out).toContain(':scope')
17
- expect(out).not.toContain(':root')
18
- expect(out).toContain('--color-primary: blue')
44
+
45
+ expect(selectorsIn(out)).toEqual([':scope'])
19
46
  })
20
47
 
21
48
  it('leaves :scope alone (idempotent)', () => {
@@ -47,7 +74,18 @@ describe('wrapChromeCss', () => {
47
74
  scopeLower: '.__poveste-render-story',
48
75
  })
49
76
  expect(out).toContain('@scope (.poveste-app-root) to (.__poveste-render-story)')
50
- expect(out).toContain('body')
77
+ expect(out).toContain('color: red')
78
+ })
79
+
80
+ // Same rule as the user side: inside the wrap, `body` means the app's root
81
+ // container, which is what :scope resolves to (#102).
82
+ it('rewrites html and body to :scope', () => {
83
+ const out = wrapChromeCss('body { font-size: .875rem }\nhtml { color-scheme: dark }', {
84
+ scopeRoot: '.poveste-app-root',
85
+ scopeLower: '.__poveste-render-story',
86
+ })
87
+ expect(out).toContain(':scope')
88
+ expect(out).not.toMatch(/(?:^|[\s,{])(?:html|body)\b/m)
51
89
  })
52
90
 
53
91
  it('hoists @import to the top, outside @scope', () => {
@@ -1,4 +1,3 @@
1
- import type { OutputBundle } from 'rollup'
2
1
  import type { Plugin as VitePlugin } from 'vite'
3
2
  import { STORY_SCOPE_ROOT } from './selectors.js'
4
3
  import { wrapUserCss } from './transforms.js'
@@ -17,7 +16,7 @@ export function entryCssMergerPlugin(opts: EntryCssMergerOptions = { isolateStyl
17
16
  name: 'poveste:style-isolation:entry-css-merger',
18
17
  apply: 'build',
19
18
  enforce: 'post',
20
- generateBundle(_options, bundle: OutputBundle) {
19
+ generateBundle(_options, bundle) {
21
20
  function collectChunkCss(entryFileName: string, entryName: string, acc: Map<string, string[]>) {
22
21
  const seen = new Set<string>()
23
22
  const queue = [entryFileName]
@@ -48,6 +47,7 @@ export function entryCssMergerPlugin(opts: EntryCssMergerOptions = { isolateStyl
48
47
  }
49
48
 
50
49
  const mergedByEntry = new Map<string, string[]>()
50
+ const mergedCssAssets: string[] = []
51
51
  for (const [cssName, entries] of cssAssetUsedBy) {
52
52
  const asset = bundle[cssName]
53
53
  if (!asset || asset.type !== 'asset') continue
@@ -59,6 +59,13 @@ export function entryCssMergerPlugin(opts: EntryCssMergerOptions = { isolateStyl
59
59
  list.push(source)
60
60
  mergedByEntry.set(entryName, list)
61
61
  }
62
+ mergedCssAssets.push(cssName)
63
+ }
64
+
65
+ // Rolldown ignores assignment to `bundle` in generateBundle, so the merged
66
+ // files are added with this.emitFile. The now-merged per-chunk CSS assets
67
+ // are dropped so they don't ship as unscoped, orphaned duplicates.
68
+ for (const cssName of mergedCssAssets) {
62
69
  delete bundle[cssName]
63
70
  }
64
71
 
@@ -68,13 +75,7 @@ export function entryCssMergerPlugin(opts: EntryCssMergerOptions = { isolateStyl
68
75
  const finalSource = entryName === mainEntryName && opts.isolateStyles
69
76
  ? wrapMarkedUserCss(merged, scopeRoot)
70
77
  : stripMarkers(merged)
71
- bundle[fileName] = {
72
- type: 'asset',
73
- fileName,
74
- name: fileName,
75
- source: finalSource,
76
- needsCodeReference: false,
77
- } as any
78
+ this.emitFile({ type: 'asset', fileName, source: finalSource })
78
79
  }
79
80
 
80
81
  // Re-point CSS metadata: entries claim their own merged file; non-entry
@@ -16,7 +16,7 @@ export function wrapUserCss(css: string, opts: WrapOptions): string {
16
16
  return css
17
17
  }
18
18
  const { hoisted, remainder } = extractHoistableAtRules(css)
19
- const body = remainder.includes(':root')
19
+ const body = hasRootSelector(remainder)
20
20
  ? rewriteRootToScope(remainder)
21
21
  : remainder
22
22
  const hoistedBlock = hoisted.length > 0 ? `${hoisted.join('\n')}\n` : ''
@@ -29,10 +29,10 @@ export function wrapChromeCss(css: string, opts: WrapWithLowerOptions): string {
29
29
  return css
30
30
  }
31
31
  const { hoisted, remainder } = extractHoistableAtRules(css)
32
- // Rewrite `:root` to `:scope` so source-level `@scope (:root) to (...)`
32
+ // Rewrite root selectors to `:scope` so source-level `@scope (:root) to (...)`
33
33
  // rules nest correctly under the outer scope; without this, the inner
34
34
  // `:root` resolves to the document root which is outside the outer scope.
35
- const body = remainder.includes(':root')
35
+ const body = hasRootSelector(remainder)
36
36
  ? rewriteRootToScope(remainder)
37
37
  : remainder
38
38
  const hoistedBlock = hoisted.length > 0 ? `${hoisted.join('\n')}\n` : ''
@@ -46,19 +46,48 @@ export function isGlobalImport(id: string): boolean {
46
46
  return parts.includes('global') || parts.some(p => p.startsWith('global='))
47
47
  }
48
48
 
49
+ // `html` and `body` sit above the scoping root once the sheet is wrapped, so a
50
+ // rule targeting either can never match. Inside a story they mean the same
51
+ // thing `:root` does — the one root there is — which is what `:scope`
52
+ // resolves to. Leaving them out made `body { font-size: 14px }` inert with no
53
+ // error (#116); the chrome wrapper learned the same lesson in #102.
54
+ const ROOT_TYPE_SELECTORS = new Set(['html', 'body'])
55
+
56
+ // Only a gate on the lightningcss round-trip, so a false positive costs a
57
+ // reparse and nothing else. Word-bounded to skip `.sidebar-body` and friends.
58
+ const ROOT_SELECTOR_RE = /:root|(?:^|[\s,{}>+~])(?:html|body)\b/i
59
+
60
+ function hasRootSelector(css: string): boolean {
61
+ return ROOT_SELECTOR_RE.test(css)
62
+ }
63
+
64
+ // Type selectors are ASCII case-insensitive against HTML and lightningcss
65
+ // reports the name as authored, so `BODY { … }` arrives spelled `BODY`.
66
+ function isRootTypeSelector(name: string): boolean {
67
+ return ROOT_TYPE_SELECTORS.has(name.toLowerCase())
68
+ }
69
+
70
+ // Top level of the selector only: lightningcss hands `:is()`/`:where()`/`:not()`
71
+ // over as one part with the inner selectors nested inside, so a root spelled
72
+ // `:is(html, body)` is left alone. Descending needs a decision on `:not()`,
73
+ // where rewriting would change what the rule matches rather than fix it (#124).
49
74
  function rewriteRootToScope(css: string): string {
75
+ const scope = { type: 'pseudo-class', kind: 'scope' } as const
50
76
  const processed = lightningcssTransform({
51
77
  filename: 'user.css',
52
78
  code: Buffer.from(css, 'utf8'),
53
79
  minify: false,
54
80
  visitor: {
55
81
  Selector(selector) {
56
- for (const part of selector) {
82
+ return selector.map((part) => {
57
83
  if (part.type === 'pseudo-class' && (part as any).kind === 'root') {
58
- ;(part as any).kind = 'scope'
84
+ return scope as unknown as typeof part
85
+ }
86
+ if (part.type === 'type' && isRootTypeSelector(part.name)) {
87
+ return scope as unknown as typeof part
59
88
  }
60
- }
61
- return selector
89
+ return part
90
+ })
62
91
  },
63
92
  },
64
93
  })
package/src/node/vite.ts CHANGED
@@ -30,9 +30,9 @@ export async function mergePovesteViteConfig(viteConfig: InlineConfig, ctx: Cont
30
30
  const command = ctx.mode === 'dev' ? 'serve' : 'build'
31
31
  const overrides = typeof ctx.config.vite === 'function'
32
32
  ? await ctx.config.vite(viteConfig as ViteConfig, {
33
- mode: ctx.mode,
34
- command,
35
- })
33
+ mode: ctx.mode,
34
+ command,
35
+ })
36
36
  : ctx.config.vite
37
37
  if (overrides) {
38
38
  viteConfig = mergeViteConfig(viteConfig, overrides)
@@ -324,7 +324,7 @@ export async function getViteConfigWithPlugins(isServer: boolean, ctx: Context):
324
324
  name: 'poveste:flags',
325
325
  enforce: 'pre',
326
326
  transform(code, id) {
327
- if (id.match(/\.(vue|js)($|\?)/)) {
327
+ if (/\.(?:vue|js)(?:$|\?)/.test(id)) {
328
328
  const original = code
329
329
  for (const flag in flags) {
330
330
  code = code.replace(new RegExp(flag, 'g'), flags[flag])
package/tsconfig.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "baseUrl": ".",
11
11
  "rootDir": "src",
12
12
  "module": "ESNext",
13
- "moduleResolution": "node",
13
+ "moduleResolution": "bundler",
14
14
  "resolveJsonModule": true,
15
15
  "types": [
16
16
  "node"