proscenium 0.1.0.alpha2-x86_64-linux → 0.1.0.alpha4-x86_64-linux

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +216 -32
  3. data/app/components/react_component.rb +10 -3
  4. data/bin/esbuild +0 -0
  5. data/bin/lightningcss +0 -0
  6. data/config/routes.rb +1 -1
  7. data/lib/proscenium/compilers/esbuild/compile_error.js +147 -0
  8. data/lib/proscenium/compilers/esbuild/css/postcss.js +67 -0
  9. data/lib/proscenium/compilers/esbuild/css_plugin.js +176 -0
  10. data/lib/proscenium/compilers/esbuild/env_plugin.js +6 -4
  11. data/lib/proscenium/compilers/esbuild/http_bundle_plugin.js +53 -0
  12. data/lib/proscenium/compilers/esbuild/import_map.js +59 -0
  13. data/lib/proscenium/compilers/esbuild/resolve_plugin.js +67 -65
  14. data/lib/proscenium/compilers/esbuild/setup_plugin.js +32 -22
  15. data/lib/proscenium/{cli → compilers}/esbuild/solidjs_plugin.js +5 -4
  16. data/lib/proscenium/compilers/esbuild.bench.js +7 -3
  17. data/lib/proscenium/compilers/esbuild.js +93 -19
  18. data/lib/proscenium/css_module.rb +48 -6
  19. data/lib/proscenium/helper.rb +5 -3
  20. data/lib/proscenium/middleware/base.rb +13 -3
  21. data/lib/proscenium/middleware/esbuild.rb +17 -1
  22. data/lib/proscenium/middleware/lightningcss.rb +64 -0
  23. data/lib/proscenium/middleware.rb +7 -19
  24. data/lib/proscenium/phlex.rb +36 -0
  25. data/lib/proscenium/railtie.rb +10 -20
  26. data/lib/proscenium/runtime/auto_reload.js +3 -3
  27. data/lib/proscenium/side_load.rb +14 -40
  28. data/lib/proscenium/utils.js +8 -0
  29. data/lib/proscenium/version.rb +1 -1
  30. data/lib/proscenium/view_component.rb +5 -1
  31. data/lib/proscenium.rb +1 -0
  32. metadata +27 -19
  33. data/bin/parcel_css +0 -0
  34. data/lib/proscenium/cli/argument_error.js +0 -24
  35. data/lib/proscenium/cli/builders/index.js +0 -1
  36. data/lib/proscenium/cli/builders/javascript.js +0 -45
  37. data/lib/proscenium/cli/builders/react.js +0 -60
  38. data/lib/proscenium/cli/builders/solid.js +0 -46
  39. data/lib/proscenium/cli/esbuild/env_plugin.js +0 -21
  40. data/lib/proscenium/cli/esbuild/resolve_plugin.js +0 -136
  41. data/lib/proscenium/cli/js_builder.js +0 -194
  42. data/lib/proscenium/cli/solid.js +0 -15
  43. data/lib/proscenium/cli/utils.js +0 -93
  44. data/lib/proscenium/middleware/parcel_css.rb +0 -37
  45. data/lib/proscenium/runtime/component_manager/index.js +0 -27
  46. data/lib/proscenium/runtime/component_manager/render_component.js +0 -40
  47. data/lib/proscenium/runtime/import_css.js +0 -46
@@ -0,0 +1,176 @@
1
+ import { crypto } from 'std/crypto/mod.ts'
2
+ import { join, resolve, dirname, basename, fromFileUrl } from 'std/path/mod.ts'
3
+
4
+ import { fileExists } from '../../utils.js'
5
+ import postcss from './css/postcss.js'
6
+ import setup from './setup_plugin.js'
7
+
8
+ export default setup('css', async build => {
9
+ const cwd = build.initialOptions.absWorkingDir
10
+ const lightningcssBin = resolve(
11
+ dirname(fromFileUrl(import.meta.url)),
12
+ '../../../../bin/lightningcss'
13
+ )
14
+
15
+ let customMedia
16
+ try {
17
+ customMedia = await Deno.readTextFile(join(cwd, 'config', 'custom_media_queries.css'))
18
+ } catch {
19
+ // do nothing, as we don't require custom media.
20
+ }
21
+
22
+ return [
23
+ {
24
+ type: 'onLoad',
25
+ filter: /\.css$/,
26
+ namespace: 'file',
27
+ async callback(args) {
28
+ const hash = await digest(args.path.slice(cwd.length))
29
+ const isCssModule = args.path.endsWith('.module.css')
30
+
31
+ // If path is a CSS module, imported from JS, and a side-loaded ViewComponent stylesheet,
32
+ // simply return a JS proxy of the class names. The stylesheet itself will have already been
33
+ // side loaded. This avoids compiling the CSS all over again.
34
+ if (isCssModule && args.pluginData?.importedFromJs && (await isViewComponent(args.path))) {
35
+ return {
36
+ resolveDir: cwd,
37
+ loader: 'js',
38
+ contents: cssModulesProxyTemplate(hash)
39
+ }
40
+ }
41
+
42
+ let cmd = [
43
+ lightningcssBin,
44
+ '--nesting',
45
+ '--error-recovery',
46
+ args.pluginData?.importedFromJs && '--minify',
47
+ '--targets',
48
+ '>= 0.25%'
49
+ ].filter(Boolean)
50
+
51
+ // This will process the CSS with Postcss only if it needs to.
52
+ let [tmpFile, contents] = await postcss(cwd, args.path)
53
+
54
+ // As custom media are defined in their own file, we have to append the file contents to our
55
+ // stylesheet, so that the custom media can be used.
56
+ if (customMedia) {
57
+ cmd.push('--custom-media')
58
+
59
+ if (!tmpFile && !contents) {
60
+ tmpFile = await Deno.makeTempFile()
61
+ contents = await Deno.readTextFile(args.path)
62
+ }
63
+
64
+ contents += customMedia
65
+ }
66
+
67
+ if (tmpFile && contents) {
68
+ await Deno.writeTextFile(tmpFile, contents)
69
+ }
70
+
71
+ if (isCssModule) {
72
+ cmd = cmd.concat(['--css-modules', '--css-modules-pattern', `[local]${hash}`])
73
+ }
74
+
75
+ const p = Deno.run({
76
+ cmd: [...cmd, tmpFile || args.path],
77
+ stdout: 'piped',
78
+ stderr: 'piped'
79
+ })
80
+
81
+ const { code } = await p.status()
82
+ const rawOutput = await p.output()
83
+ const rawError = await p.stderrOutput()
84
+
85
+ // Even though Deno docs say that reading the outputs (above) closes their pipes, warnings
86
+ // are raised during tests that the child process have not been closed. So we manually close
87
+ // here.
88
+ p.close()
89
+
90
+ // Success!
91
+ if (code === 0) {
92
+ let contents = new TextDecoder().decode(rawOutput)
93
+ if (isCssModule) {
94
+ contents = JSON.parse(contents)
95
+ }
96
+
97
+ // If stylesheet is imported from JS, then we return JS code that appends the stylesheet
98
+ // in a <style> in the <head> of the page, and if the stylesheet is a CSS module, it
99
+ // exports a plain object of class names.
100
+ if (args.pluginData?.importedFromJs) {
101
+ const code = isCssModule ? contents.code : contents
102
+ const mod = [
103
+ `let e = document.querySelector('#_${hash}');`,
104
+ 'if (!e) {',
105
+ "e = document.createElement('style');",
106
+ `e.id = '_${hash}';`,
107
+ 'document.head.appendChild(e);',
108
+ `e.appendChild(document.createTextNode(\`${code}\`));`,
109
+ '}'
110
+ ]
111
+
112
+ if (isCssModule) {
113
+ const classes = {}
114
+ for (const key in contents.exports) {
115
+ if (Object.hasOwnProperty.call(contents.exports, key)) {
116
+ classes[key] = contents.exports[key].name
117
+ }
118
+ }
119
+ mod.push(`export default ${JSON.stringify(classes)};`)
120
+ }
121
+
122
+ // We are importing from JS, so return the entire result from LightningCSS via the js
123
+ // loader.
124
+ return {
125
+ resolveDir: cwd,
126
+ loader: 'js',
127
+ contents: mod.join('')
128
+ }
129
+ }
130
+
131
+ return { loader: 'css', contents: isCssModule ? contents.code : contents }
132
+ } else {
133
+ const errorString = new TextDecoder().decode(rawError)
134
+ throw errorString
135
+ }
136
+ }
137
+ }
138
+ ]
139
+ })
140
+
141
+ async function digest(value) {
142
+ value = new TextEncoder().encode(value)
143
+ const view = new DataView(await crypto.subtle.digest('SHA-1', value))
144
+
145
+ let hexCodes = ''
146
+ for (let index = 0; index < view.byteLength; index += 4) {
147
+ hexCodes += view.getUint32(index).toString(16).padStart(8, '0')
148
+ }
149
+
150
+ return hexCodes.slice(0, 8)
151
+ }
152
+
153
+ async function isViewComponent(path) {
154
+ const fileName = basename(path)
155
+ const dirName = dirname(path)
156
+
157
+ return (
158
+ (fileName === 'component.module.css' && (await fileExists(join(dirName, 'component.rb')))) ||
159
+ (fileName.endsWith('_component.module.css') &&
160
+ (await fileExists(join(dirName, fileName.replace(/\.module\.css$/, '.rb')))))
161
+ )
162
+ }
163
+
164
+ function cssModulesProxyTemplate(hash) {
165
+ return [
166
+ `export default new Proxy( {}, {`,
167
+ ` get(target, prop, receiver) {`,
168
+ ` if (prop in target || typeof prop === 'symbol') {`,
169
+ ` return Reflect.get(target, prop, receiver)`,
170
+ ` } else {`,
171
+ ` return prop + '${hash}'`,
172
+ ` }`,
173
+ ` }`,
174
+ `})`
175
+ ].join('')
176
+ }
@@ -1,15 +1,17 @@
1
1
  import setup from './setup_plugin.js'
2
2
 
3
3
  export default setup('env', () => {
4
- return {
5
- onResolve: {
4
+ return [
5
+ {
6
+ type: 'onResolve',
6
7
  filter: /^env$/,
7
8
  callback({ path }) {
8
9
  return { path, namespace: 'env' }
9
10
  }
10
11
  },
11
12
 
12
- onLoad: {
13
+ {
14
+ type: 'onLoad',
13
15
  filter: /.*/,
14
16
  namespace: 'env',
15
17
  callback() {
@@ -17,5 +19,5 @@ export default setup('env', () => {
17
19
  return { loader: 'json', contents: JSON.stringify(env) }
18
20
  }
19
21
  }
20
- }
22
+ ]
21
23
  })
@@ -0,0 +1,53 @@
1
+ import setup from './setup_plugin.js'
2
+
3
+ export default setup('httpBundle', () => {
4
+ return [
5
+ {
6
+ type: 'onResolve',
7
+ filter: /^https?:\/\//,
8
+ callback(args) {
9
+ let queryParams, suffix
10
+ if (args.path.includes('?')) {
11
+ const [path, query] = args.path.split('?')
12
+ queryParams = new URLSearchParams(query)
13
+ suffix = `?${query}`
14
+ args.path = path
15
+ }
16
+
17
+ if (queryParams?.has('bundle')) {
18
+ return { path: args.path, namespace: 'httpBundle', suffix }
19
+ } else {
20
+ return { external: true }
21
+ }
22
+ }
23
+ },
24
+
25
+ // Intercept all import paths inside downloaded files and resolve them against the original URL.
26
+ {
27
+ type: 'onResolve',
28
+ filter: /.*/,
29
+ namespace: 'httpBundle',
30
+ callback(args) {
31
+ return {
32
+ path: new URL(args.path, args.importer).toString(),
33
+ namespace: 'httpBundle'
34
+ }
35
+ }
36
+ },
37
+
38
+ // Download and return the content.
39
+ //
40
+ // TODO: cache this!
41
+ {
42
+ type: 'onLoad',
43
+ filter: /.*/,
44
+ namespace: 'httpBundle',
45
+ async callback(args) {
46
+ const textResponse = await fetch(args.path)
47
+ const contents = await textResponse.text()
48
+
49
+ return { contents }
50
+ }
51
+ }
52
+ ]
53
+ })
@@ -0,0 +1,59 @@
1
+ import { join } from 'std/path/mod.ts'
2
+ import { parseFromString } from 'import-maps/resolve'
3
+
4
+ const baseURL = new URL('file://')
5
+
6
+ class ImportMapError extends Error {
7
+ constructor(fileName, ...params) {
8
+ super(...params)
9
+
10
+ if (Error.captureStackTrace) {
11
+ Error.captureStackTrace(this, ImportMapError)
12
+ }
13
+
14
+ this.name = 'ImportMapError'
15
+ this.file = fileName
16
+ }
17
+ }
18
+
19
+ export function readImportMap(fileName, rootDir) {
20
+ let importMap
21
+
22
+ if (fileName) {
23
+ importMap = readFile(fileName, rootDir, true)
24
+ } else {
25
+ fileName = ['config/import_map.json', 'config/import_map.js'].find(f => {
26
+ const result = readFile(f, rootDir)
27
+ if (result) {
28
+ importMap = result
29
+ return true
30
+ }
31
+ })
32
+ }
33
+
34
+ return importMap
35
+ }
36
+
37
+ function readFile(file, rootDir, required = false) {
38
+ let contents = null
39
+
40
+ try {
41
+ contents = Deno.readTextFileSync(join(rootDir, file))
42
+ } catch (error) {
43
+ if (required) {
44
+ throw new ImportMapError(file, error.message, { cause: error })
45
+ }
46
+ }
47
+
48
+ if (contents === null) return null
49
+
50
+ try {
51
+ if (file.endsWith('.js')) {
52
+ contents = JSON.stringify(eval(contents)(Deno.env.get('RAILS_ENV')))
53
+ }
54
+
55
+ return parseFromString(contents, baseURL)
56
+ } catch (error) {
57
+ throw new ImportMapError(file, error.message, { cause: error })
58
+ }
59
+ }
@@ -1,36 +1,50 @@
1
1
  import { join, resolve } from 'std/path/mod.ts'
2
- import {
3
- parseFromString as parseImportMap,
4
- resolve as resolveFromImportMap
5
- } from 'import-maps/resolve'
2
+ import { resolve as resolveFromImportMap } from 'import-maps/resolve'
6
3
 
7
4
  import setup from './setup_plugin.js'
8
5
 
9
- const baseURL = new URL('file://')
10
- const importKinds = ['import-statement', 'dynamic-import', 'require-call']
6
+ const importKinds = ['import-statement', 'dynamic-import', 'require-call', 'import-rule']
11
7
 
12
8
  export default setup('resolve', (build, options) => {
13
- const { runtimeDir } = options
9
+ const { runtimeDir, importMap } = options
14
10
  const cwd = build.initialOptions.absWorkingDir
15
11
  const runtimeCwdAlias = `${cwd}/proscenium-runtime`
16
- const importMap = readImportMap()
12
+ let bundled = false
17
13
 
18
- return {
19
- onResolve: {
14
+ return [
15
+ {
16
+ type: 'onResolve',
20
17
  filter: /.*/,
21
18
  async callback(args) {
22
- // Remote modules
19
+ if (args.path.includes('?')) {
20
+ const [path, query] = args.path.split('?')
21
+ args.path = path
22
+ args.suffix = `?${query}`
23
+ args.queryParams = new URLSearchParams(query)
24
+ }
25
+
26
+ // Mark remote modules as external.
23
27
  if (args.path.startsWith('http://') || args.path.startsWith('https://')) {
24
28
  return { external: true }
25
29
  }
26
30
 
27
31
  // Proscenium runtime
28
- if (args.path.startsWith('@proscenium/')) {
29
- return {
30
- path: `${args.path.replace(/^@proscenium/, '/proscenium-runtime')}/index.js`,
31
- external: true
32
- }
33
- }
32
+ // if (args.path.startsWith('@proscenium/')) {
33
+ // const result = { suffix: args.suffix }
34
+
35
+ // if (args.queryParams?.has('bundle-all')) {
36
+ // bundled = true
37
+ // }
38
+
39
+ // if (bundled || args.queryParams?.has('bundle')) {
40
+ // result.path = join(runtimeDir, `${args.path.replace(/^@proscenium/, '')}/index.js`)
41
+ // } else {
42
+ // result.path = `${args.path.replace(/^@proscenium/, '/proscenium-runtime')}/index.js`
43
+ // result.external = true
44
+ // }
45
+
46
+ // return result
47
+ // }
34
48
 
35
49
  if (args.path.startsWith(runtimeCwdAlias)) {
36
50
  return { path: join(runtimeDir, args.path.slice(runtimeCwdAlias.length)) }
@@ -41,33 +55,8 @@ export default setup('resolve', (build, options) => {
41
55
  return await unbundleImport(args)
42
56
  }
43
57
  }
44
- },
45
-
46
- onLoad: {
47
- filter: /.*/,
48
- namespace: 'importStylesheet',
49
- callback(args) {
50
- const result = {
51
- resolveDir: cwd,
52
- loader: 'js'
53
- }
54
-
55
- if (args.path.endsWith('.module.css')) {
56
- result.contents = `
57
- import { importCssModule } from '/proscenium-runtime/import_css.js'
58
- export default await importCssModule('${args.path}')
59
- `
60
- } else {
61
- result.contents = `
62
- import { appendStylesheet } from '/proscenium-runtime/import_css.js'
63
- appendStylesheet('${args.path}')
64
- `
65
- }
66
-
67
- return result
68
- }
69
58
  }
70
- }
59
+ ]
71
60
 
72
61
  // Resolve the given `params.path` to a path relative to the Rails root.
73
62
  //
@@ -76,10 +65,12 @@ export default setup('resolve', (build, options) => {
76
65
  // './my_module' -> '/.../app/my_module.js'
77
66
  // '/app/my_module' -> '/.../app/my_module.js'
78
67
  async function unbundleImport(params) {
79
- const result = { path: params.path }
68
+ const result = { path: params.path, suffix: params.suffix }
80
69
 
81
70
  if (importMap) {
71
+ const baseURL = new URL(params.importer.slice(cwd.length), 'file://')
82
72
  const { matched, resolvedImport } = resolveFromImportMap(params.path, importMap, baseURL)
73
+
83
74
  if (matched) {
84
75
  if (resolvedImport.protocol === 'file:') {
85
76
  params.path = resolvedImport.pathname
@@ -97,18 +88,43 @@ export default setup('resolve', (build, options) => {
97
88
  // Resolve the path using esbuild's internal resolution. This allows us to import node packages
98
89
  // and extension-less paths without custom code, as esbuild with resolve them for us.
99
90
  const resolveResult = await build.resolve(result.path, {
100
- resolveDir: params.resolveDir,
91
+ // If path is a bare module (node_modules), and resolveDir is the Proscenium runtime dir, then
92
+ // use `cwd` as the `resolveDir`, otherwise pass it through as is. This ensures that nested
93
+ // node_modules are resolved correctly.
94
+ resolveDir:
95
+ isBareModule(result.path) && params.resolveDir.startsWith(runtimeDir)
96
+ ? cwd
97
+ : params.resolveDir,
101
98
  pluginData: {
102
99
  // We use this property later on, as we should ignore this resolution call.
103
100
  isResolvingPath: true
104
101
  }
105
102
  })
106
103
 
107
- if (resolveResult.errors.length > 0) {
108
- // throw `${resolveResult.errors[0].text} (resolveDir: ${cwd})`
104
+ // Simple return the resolved result if we have an error. Usually happens when module is not
105
+ // found.
106
+ if (resolveResult.errors.length > 0) return resolveResult
107
+
108
+ // If 'bundle-all' queryParam is defined, return the resolveResult.
109
+ if (bundled || params.queryParams?.has('bundle-all')) {
110
+ bundled = true
111
+ return { ...resolveResult, suffix: '?bundle-all' }
112
+ }
113
+
114
+ // If 'bundle' queryParam is defined, return the resolveResult.
115
+ if (params.queryParams?.has('bundle')) {
116
+ return { ...resolveResult, suffix: '?bundle' }
117
+ }
118
+
119
+ if (resolveResult.path.startsWith(runtimeDir)) {
120
+ result.path = '/proscenium-runtime' + resolveResult.path.slice(runtimeDir.length)
121
+ } else if (!resolveResult.path.startsWith(cwd)) {
122
+ // If resolved path does not start with cwd, then it is most likely linked, so bundle it.
123
+ return { ...resolveResult, suffix: '?bundle' }
124
+ } else {
125
+ result.path = resolveResult.path.slice(cwd.length)
109
126
  }
110
127
 
111
- result.path = resolveResult.path.slice(cwd.length)
112
128
  result.sideEffects = resolveResult.sideEffects
113
129
 
114
130
  if (
@@ -117,29 +133,15 @@ export default setup('resolve', (build, options) => {
117
133
  /\.jsx?$/.test(params.importer)
118
134
  ) {
119
135
  // We're importing a CSS file from JS(X).
120
- result.namespace = 'importStylesheet'
136
+ return { ...resolveResult, pluginData: { importedFromJs: true } }
121
137
  } else {
122
- // Requested path is a bare module.
123
138
  result.external = true
124
139
  }
125
140
 
126
141
  return result
127
142
  }
128
-
129
- function readImportMap() {
130
- const file = join(cwd, 'config', 'import_map.json')
131
- let source
132
-
133
- try {
134
- source = Deno.readTextFileSync(file)
135
- } catch {
136
- return null
137
- }
138
-
139
- return parseImportMap(source, baseURL)
140
- }
141
143
  })
142
144
 
143
- function isBareModule(path) {
144
- return !path.startsWith('.') && !path.startsWith('/')
145
+ function isBareModule(mod) {
146
+ return !mod.startsWith('/') && !mod.startsWith('.')
145
147
  }
@@ -1,35 +1,45 @@
1
1
  export default (pluginName, pluginFn) => {
2
2
  return (options = {}) => ({
3
3
  name: pluginName,
4
- setup(build) {
5
- const plugin = pluginFn(build, options)
4
+ async setup(build) {
5
+ const callbacks = await pluginFn(build, options)
6
6
 
7
- if (plugin.onResolve) {
8
- const { callback, ...onResolve } = plugin.onResolve
7
+ callbacks.forEach(({ type, callback, filter, namespace }) => {
8
+ if (type === 'onResolve') {
9
+ build.onResolve({ filter, namespace }, async params => {
10
+ if (params.pluginData?.isResolvingPath) return
9
11
 
10
- build.onResolve(onResolve, async params => {
11
- if (params.pluginData?.isResolvingPath) return
12
+ let results
12
13
 
13
- options.debug && console.debug(`plugin(${pluginName}):onResolve`, params.path, { params })
14
- const results = await callback(params)
15
- options.debug &&
16
- console.debug(`plugin(${pluginName}):onResolve`, params.path, { results })
14
+ if (options.debug) {
15
+ console.debug()
16
+ console.group(`plugin(${pluginName}):onResolve`, { filter, namespace })
17
+ console.debug('params:', params)
17
18
 
18
- return results
19
- })
20
- }
19
+ try {
20
+ results = await callback(params)
21
+ console.debug('results:', results)
22
+ } finally {
23
+ console.groupEnd()
24
+ }
25
+ } else {
26
+ results = await callback(params)
27
+ }
21
28
 
22
- if (plugin.onLoad) {
23
- const { callback, ...onLoad } = plugin.onLoad
29
+ return results
30
+ })
31
+ } else if (type === 'onLoad') {
32
+ build.onLoad({ filter, namespace }, async params => {
33
+ options.debug && console.debug(`plugin(${pluginName}):onLoad`, { params })
24
34
 
25
- build.onLoad(onLoad, params => {
26
- options.debug && console.debug(`plugin(${pluginName}):onLoad`, { params })
27
- const results = callback(params)
28
- options.debug && console.debug(`plugin(${pluginName}):onLoad`, { results })
35
+ const results = await callback(params)
29
36
 
30
- return results
31
- })
32
- }
37
+ options.debug && console.debug(`plugin(${pluginName}):onLoad`, { results })
38
+
39
+ return results
40
+ })
41
+ }
42
+ })
33
43
  }
34
44
  })
35
45
  }
@@ -2,11 +2,12 @@ import { basename } from 'std/path/mod.ts'
2
2
  import { transformAsync } from '@babel/core'
3
3
  import solid from 'babel-preset-solid'
4
4
 
5
- import { setup } from '../utils.js'
5
+ import { setup } from './setup_plugin.js'
6
6
 
7
7
  export default setup('solidjs', () => {
8
- return {
9
- onLoad: {
8
+ return [
9
+ {
10
+ type: 'onLoad',
10
11
  filter: /\.jsx$/,
11
12
  async callback(args) {
12
13
  const source = await Deno.readTextFile(args.path)
@@ -19,5 +20,5 @@ export default setup('solidjs', () => {
19
20
  return { contents: code, loader: 'js' }
20
21
  }
21
22
  }
22
- }
23
+ ]
23
24
  })
@@ -2,8 +2,12 @@ import { join } from 'std/path/mod.ts'
2
2
 
3
3
  import compile from './esbuild.js'
4
4
 
5
- const cwd = join(Deno.cwd(), 'test', 'internal')
5
+ const root = join(Deno.cwd(), 'test', 'internal')
6
6
 
7
- Deno.bench('esbuild', async () => {
8
- await compile(cwd, 'lib/foo.js')
7
+ Deno.bench('esbuild js', async () => {
8
+ await compile(['lib/foo.js'], { root })
9
+ })
10
+
11
+ Deno.bench('esbuild css', async () => {
12
+ await compile(['lib/foo.css'], { root })
9
13
  })