@jsenv/plugin-bundling 1.0.0

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.
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@jsenv/plugin-bundling",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/jsenv/jsenv-core",
8
+ "directory": "packages/jsenv-plugin-bundling"
9
+ },
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "type": "module",
14
+ "exports": {
15
+ ".": {
16
+ "import": "./src/main.js"
17
+ },
18
+ "./*": "./*"
19
+ },
20
+ "files": [
21
+ "/src/"
22
+ ],
23
+ "scripts": {
24
+ "test": "node --conditions=development ./scripts/test.mjs"
25
+ },
26
+ "dependencies": {
27
+ "@jsenv/ast": "1.4.8",
28
+ "@jsenv/babel-plugins": "1.1.0",
29
+ "@jsenv/log": "3.3.2",
30
+ "@jsenv/url-meta": "7.0.0",
31
+ "@jsenv/urls": "1.2.8",
32
+ "@jsenv/sourcemap": "1.0.8",
33
+ "acorn-import-assertions": "1.8.0",
34
+ "rollup": "3.7.4"
35
+ },
36
+ "peerDependencies": {
37
+ "@jsenv/core": "*"
38
+ },
39
+ "devDependencies": {
40
+ "@jsenv/core": "../../"
41
+ }
42
+ }
@@ -0,0 +1,140 @@
1
+ /*
2
+ * Each @import found in css is replaced by the file content
3
+ * - There is no need to worry about urls (such as background-image: url())
4
+ * because they are absolute (file://*) and will be made relative again by jsenv build
5
+ * - The sourcemap are not generated but ideally they should be
6
+ * It can be quite challenging, see "bundle_sourcemap.js"
7
+ */
8
+
9
+ import { createMagicSource } from "@jsenv/sourcemap"
10
+ import { applyPostCss, postCssPluginUrlVisitor } from "@jsenv/ast"
11
+
12
+ import { sortByDependencies } from "./sort_by_dependencies.js"
13
+
14
+ // Do not use until https://github.com/parcel-bundler/parcel-css/issues/181
15
+ export const bundleCss = async ({ cssUrlInfos, context }) => {
16
+ const bundledCssUrlInfos = {}
17
+ const cssBundleInfos = await performCssBundling({
18
+ cssEntryUrlInfos: cssUrlInfos,
19
+ context,
20
+ })
21
+ cssUrlInfos.forEach((cssUrlInfo) => {
22
+ bundledCssUrlInfos[cssUrlInfo.url] = {
23
+ data: {
24
+ bundlerName: "parcel",
25
+ },
26
+ contentType: "text/css",
27
+ content: cssBundleInfos[cssUrlInfo.url].bundleContent,
28
+ }
29
+ })
30
+ return bundledCssUrlInfos
31
+ }
32
+
33
+ const performCssBundling = async ({ cssEntryUrlInfos, context }) => {
34
+ const cssBundleInfos = await loadCssUrls({
35
+ cssEntryUrlInfos,
36
+ context,
37
+ })
38
+ const cssUrlsSorted = sortByDependencies(cssBundleInfos)
39
+ cssUrlsSorted.forEach((cssUrl) => {
40
+ const cssBundleInfo = cssBundleInfos[cssUrl]
41
+ const magicSource = createMagicSource(cssBundleInfo.content)
42
+ cssBundleInfo.cssUrls.forEach((cssUrl) => {
43
+ if (cssUrl.type === "@import") {
44
+ magicSource.replace({
45
+ start: cssUrl.atRuleStart,
46
+ end: cssUrl.atRuleEnd,
47
+ replacement: cssBundleInfos[cssUrl.url].bundleContent,
48
+ })
49
+ }
50
+ })
51
+ const { content } = magicSource.toContentAndSourcemap()
52
+ cssBundleInfo.bundleContent = content.trim()
53
+ })
54
+ return cssBundleInfos
55
+ }
56
+
57
+ const parseCssUrls = async ({ css, url }) => {
58
+ const cssUrls = []
59
+ await applyPostCss({
60
+ sourcemaps: false,
61
+ plugins: [
62
+ postCssPluginUrlVisitor({
63
+ urlVisitor: ({
64
+ type,
65
+ specifier,
66
+ specifierStart,
67
+ specifierEnd,
68
+ atRuleStart,
69
+ atRuleEnd,
70
+ }) => {
71
+ cssUrls.push({
72
+ type,
73
+ url: new URL(specifier, url).href,
74
+ specifierStart,
75
+ specifierEnd,
76
+ atRuleStart,
77
+ atRuleEnd,
78
+ })
79
+ },
80
+ }),
81
+ ],
82
+ url,
83
+ content: css,
84
+ })
85
+
86
+ return cssUrls
87
+ }
88
+
89
+ const loadCssUrls = async ({ cssEntryUrlInfos, context }) => {
90
+ const cssBundleInfos = {}
91
+ const promises = []
92
+ const promiseMap = new Map()
93
+
94
+ const load = (cssUrlInfo) => {
95
+ const promiseFromData = promiseMap.get(cssUrlInfo.url)
96
+ if (promiseFromData) return promiseFromData
97
+ const promise = _load(cssUrlInfo)
98
+ promises.push(promise)
99
+ promiseMap.set(cssUrlInfo.url, promise)
100
+ return promise
101
+ }
102
+
103
+ const _load = async (cssUrlInfo) => {
104
+ const cssUrls = await parseCssUrls({
105
+ css: cssUrlInfo.content,
106
+ url: cssUrlInfo.url,
107
+ })
108
+ const cssBundleInfo = {
109
+ content: cssUrlInfo.content,
110
+ cssUrls,
111
+ dependencies: [],
112
+ }
113
+ cssBundleInfos[cssUrlInfo.url] = cssBundleInfo
114
+ cssUrls.forEach((cssUrl) => {
115
+ if (cssUrl.type === "@import") {
116
+ cssBundleInfo.dependencies.push(cssUrl.url)
117
+ const importedCssUrlInfo = context.urlGraph.getUrlInfo(cssUrl.url)
118
+ load(importedCssUrlInfo)
119
+ }
120
+ })
121
+ }
122
+
123
+ cssEntryUrlInfos.forEach((cssEntryUrlInfo) => {
124
+ load(cssEntryUrlInfo)
125
+ })
126
+
127
+ const waitAll = async () => {
128
+ if (promises.length === 0) {
129
+ return
130
+ }
131
+ const promisesToWait = promises.slice()
132
+ promises.length = 0
133
+ await Promise.all(promisesToWait)
134
+ await waitAll()
135
+ }
136
+ await waitAll()
137
+ promiseMap.clear()
138
+
139
+ return cssBundleInfos
140
+ }
@@ -0,0 +1,28 @@
1
+ export const sortByDependencies = (nodes) => {
2
+ const visited = []
3
+ const sorted = []
4
+ const circular = []
5
+ const visit = (url) => {
6
+ const isSorted = sorted.includes(url)
7
+ if (isSorted) {
8
+ return
9
+ }
10
+ const isVisited = visited.includes(url)
11
+ if (isVisited) {
12
+ if (!circular.includes(url)) {
13
+ circular.push(url)
14
+ }
15
+ } else {
16
+ visited.push(url)
17
+ nodes[url].dependencies.forEach((dependencyUrl) => {
18
+ visit(dependencyUrl, url)
19
+ })
20
+ sorted.push(url)
21
+ }
22
+ }
23
+ Object.keys(nodes).forEach((url) => {
24
+ visit(url)
25
+ })
26
+ sorted.circular = circular
27
+ return sorted
28
+ }
@@ -0,0 +1,13 @@
1
+ /*
2
+ * TODO:
3
+ * for each js_classic where subtype is a worker
4
+ * take the url info and find importScripts calls
5
+ * and replace them with the corresponding url info file content
6
+ * we'll ikely need to save the importScripts node location to be able to do that
7
+ */
8
+
9
+ // import { createMagicSource } from "@jsenv/utils/sourcemap/magic_source.js"
10
+
11
+ export const bundleJsClassicWorkers = () => {
12
+ return {}
13
+ }
@@ -0,0 +1,386 @@
1
+ import { pathToFileURL } from "node:url"
2
+
3
+ import { URL_META } from "@jsenv/url-meta"
4
+ import { isFileSystemPath } from "@jsenv/urls"
5
+ import { createDetailedMessage } from "@jsenv/log"
6
+ import { babelHelperNameFromUrl } from "@jsenv/babel-plugins"
7
+ import { sourcemapConverter } from "@jsenv/sourcemap"
8
+
9
+ import { fileUrlConverter } from "./file_url_converter.js"
10
+
11
+ export const bundleJsModules = async ({
12
+ jsModuleUrlInfos,
13
+ context,
14
+ options,
15
+ }) => {
16
+ const {
17
+ signal,
18
+ logger,
19
+ rootDirectoryUrl,
20
+ buildDirectoryUrl,
21
+ assetsDirectory,
22
+ urlGraph,
23
+ runtimeCompat,
24
+ sourcemaps,
25
+ } = context
26
+ const {
27
+ babelHelpersChunk = true,
28
+ include,
29
+ preserveDynamicImport = false,
30
+ strictExports = false,
31
+ rollup,
32
+ rollupInput = {},
33
+ rollupOutput = {},
34
+ rollupPlugins = [],
35
+ } = options
36
+
37
+ const resultRef = { current: null }
38
+ try {
39
+ await applyRollupPlugins({
40
+ rollup,
41
+ rollupPlugins: [
42
+ ...rollupPlugins,
43
+ rollupPluginJsenv({
44
+ signal,
45
+ logger,
46
+ rootDirectoryUrl,
47
+ buildDirectoryUrl,
48
+ assetsDirectory,
49
+ urlGraph,
50
+ jsModuleUrlInfos,
51
+
52
+ runtimeCompat,
53
+ sourcemaps,
54
+ include,
55
+ babelHelpersChunk,
56
+ preserveDynamicImport,
57
+ strictExports,
58
+ resultRef,
59
+ }),
60
+ ],
61
+ rollupInput: {
62
+ input: [],
63
+ onwarn: (warning) => {
64
+ if (warning.code === "CIRCULAR_DEPENDENCY") {
65
+ return
66
+ }
67
+ if (warning.code === "THIS_IS_UNDEFINED") {
68
+ const urlInfo = urlGraph.getUrlInfo(pathToFileURL(warning.id).href)
69
+ if (urlInfo && urlInfo.data.isBabelClientFile) {
70
+ return
71
+ }
72
+ }
73
+ if (warning.code === "EVAL") {
74
+ // ideally we should disable only for jsenv files
75
+ return
76
+ }
77
+ logger.warn(String(warning))
78
+ },
79
+ ...rollupInput,
80
+ },
81
+ rollupOutput: {
82
+ ...rollupOutput,
83
+ },
84
+ })
85
+ return resultRef.current.jsModuleBundleUrlInfos
86
+ } catch (e) {
87
+ if (e.code === "MISSING_EXPORT") {
88
+ const detailedMessage = createDetailedMessage(e.message, {
89
+ frame: e.frame,
90
+ })
91
+ throw new Error(detailedMessage, { cause: e })
92
+ }
93
+ throw e
94
+ }
95
+ }
96
+
97
+ const rollupPluginJsenv = ({
98
+ // logger,
99
+ rootDirectoryUrl,
100
+ buildDirectoryUrl,
101
+ assetsDirectory,
102
+ urlGraph,
103
+ jsModuleUrlInfos,
104
+ sourcemaps,
105
+
106
+ include,
107
+ babelHelpersChunk,
108
+ preserveDynamicImport,
109
+ strictExports,
110
+
111
+ resultRef,
112
+ }) => {
113
+ let _rollupEmitFile = () => {
114
+ throw new Error("not implemented")
115
+ }
116
+ const format = jsModuleUrlInfos.some((jsModuleUrlInfo) =>
117
+ jsModuleUrlInfo.filename.endsWith(".cjs"),
118
+ )
119
+ ? "cjs"
120
+ : "esm"
121
+ const emitChunk = (chunk) => {
122
+ return _rollupEmitFile({
123
+ type: "chunk",
124
+ ...chunk,
125
+ })
126
+ }
127
+ let importCanBeBundled = () => true
128
+ if (include) {
129
+ const associations = URL_META.resolveAssociations(
130
+ { bundle: include },
131
+ rootDirectoryUrl,
132
+ )
133
+ importCanBeBundled = (url) => {
134
+ return URL_META.applyAssociations({ url, associations }).bundle
135
+ }
136
+ }
137
+ const urlImporters = {}
138
+
139
+ return {
140
+ name: "jsenv",
141
+ async buildStart() {
142
+ _rollupEmitFile = (...args) => this.emitFile(...args)
143
+ let previousNonEntryPointModuleId
144
+ jsModuleUrlInfos.forEach((jsModuleUrlInfo) => {
145
+ const id = jsModuleUrlInfo.url
146
+ if (jsModuleUrlInfo.isEntryPoint) {
147
+ emitChunk({
148
+ id,
149
+ })
150
+ return
151
+ }
152
+ emitChunk({
153
+ id,
154
+ implicitlyLoadedAfterOneOf: previousNonEntryPointModuleId
155
+ ? [previousNonEntryPointModuleId]
156
+ : null,
157
+ preserveSignature: strictExports
158
+ ? "strict"
159
+ : jsModuleUrlInfo.dependents.size < 2
160
+ ? "allow-extension"
161
+ : "strict",
162
+ })
163
+ previousNonEntryPointModuleId = id
164
+ })
165
+ },
166
+ async generateBundle(outputOptions, rollupResult) {
167
+ _rollupEmitFile = (...args) => this.emitFile(...args)
168
+
169
+ const jsModuleBundleUrlInfos = {}
170
+ Object.keys(rollupResult).forEach((fileName) => {
171
+ const rollupFileInfo = rollupResult[fileName]
172
+ // there is 3 types of file: "placeholder", "asset", "chunk"
173
+ if (rollupFileInfo.type === "chunk") {
174
+ const sourceUrls = Object.keys(rollupFileInfo.modules).map((id) =>
175
+ fileUrlConverter.asFileUrl(id),
176
+ )
177
+
178
+ let url
179
+ let originalUrl
180
+ if (rollupFileInfo.facadeModuleId) {
181
+ url = fileUrlConverter.asFileUrl(rollupFileInfo.facadeModuleId)
182
+ originalUrl = url
183
+ } else {
184
+ url = new URL(rollupFileInfo.fileName, buildDirectoryUrl).href
185
+ if (rollupFileInfo.isDynamicEntry) {
186
+ originalUrl = sourceUrls[sourceUrls.length - 1]
187
+ } else {
188
+ originalUrl = url
189
+ }
190
+ }
191
+
192
+ const jsModuleBundleUrlInfo = {
193
+ url,
194
+ originalUrl,
195
+ type: format === "esm" ? "js_module" : "common_js",
196
+ data: {
197
+ bundlerName: "rollup",
198
+ bundleRelativeUrl: rollupFileInfo.fileName,
199
+ usesImport:
200
+ rollupFileInfo.imports.length > 0 ||
201
+ rollupFileInfo.dynamicImports.length > 0,
202
+ isDynamicEntry: rollupFileInfo.isDynamicEntry,
203
+ },
204
+ sourceUrls,
205
+ contentType: "text/javascript",
206
+ content: rollupFileInfo.code,
207
+ sourcemap: rollupFileInfo.map,
208
+ }
209
+ jsModuleBundleUrlInfos[url] = jsModuleBundleUrlInfo
210
+ }
211
+ })
212
+ resultRef.current = {
213
+ jsModuleBundleUrlInfos,
214
+ }
215
+ },
216
+ outputOptions: (outputOptions) => {
217
+ // const sourcemapFile = buildDirectoryUrl
218
+ Object.assign(outputOptions, {
219
+ format,
220
+ dir: fileUrlConverter.asFilePath(buildDirectoryUrl),
221
+ sourcemap: sourcemaps === "file" || sourcemaps === "inline",
222
+ // sourcemapFile,
223
+ sourcemapPathTransform: (relativePath) => {
224
+ return new URL(relativePath, buildDirectoryUrl).href
225
+ },
226
+ entryFileNames: () => {
227
+ return `[name].js`
228
+ },
229
+ chunkFileNames: (chunkInfo) => {
230
+ const insideJs = willBeInsideJsDirectory({
231
+ chunkInfo,
232
+ fileUrlConverter,
233
+ jsModuleUrlInfos,
234
+ })
235
+ let nameFromUrlInfo
236
+ if (chunkInfo.facadeModuleId) {
237
+ const url = fileUrlConverter.asFileUrl(chunkInfo.facadeModuleId)
238
+ const urlInfo = jsModuleUrlInfos.find(
239
+ (jsModuleUrlInfo) => jsModuleUrlInfo.url === url,
240
+ )
241
+ if (urlInfo) {
242
+ nameFromUrlInfo = urlInfo.filename
243
+ }
244
+ }
245
+ const name = nameFromUrlInfo || `${chunkInfo.name}.js`
246
+ return insideJs ? `${assetsDirectory}js/${name}` : `${name}`
247
+ },
248
+ manualChunks: (id) => {
249
+ if (babelHelpersChunk) {
250
+ const fileUrl = fileUrlConverter.asFileUrl(id)
251
+ if (
252
+ fileUrl.endsWith(
253
+ "babel-plugin-transform-async-to-promises/helpers.mjs",
254
+ )
255
+ ) {
256
+ return "babel_helpers"
257
+ }
258
+ if (babelHelperNameFromUrl(fileUrl)) {
259
+ return "babel_helpers"
260
+ }
261
+ const urlInfo = urlGraph.getUrlInfo(fileUrl)
262
+ if (urlInfo && urlInfo.data.isBabelClientFile) {
263
+ return "babel_helpers"
264
+ }
265
+ }
266
+ return null
267
+ },
268
+ // https://rollupjs.org/guide/en/#outputpaths
269
+ // paths: (id) => {
270
+ // return id
271
+ // },
272
+ })
273
+ },
274
+ // https://rollupjs.org/guide/en/#resolvedynamicimport
275
+ resolveDynamicImport: (specifier, importer) => {
276
+ if (preserveDynamicImport) {
277
+ let urlObject
278
+ if (specifier[0] === "/") {
279
+ urlObject = new URL(specifier.slice(1), rootDirectoryUrl)
280
+ } else {
281
+ if (isFileSystemPath(importer)) {
282
+ importer = fileUrlConverter.asFileUrl(importer)
283
+ }
284
+ urlObject = new URL(specifier, importer)
285
+ }
286
+ urlObject.searchParams.set("as_js_classic_library", "")
287
+ return { external: true, id: urlObject.href }
288
+ }
289
+ return null
290
+ },
291
+ resolveId: (specifier, importer = rootDirectoryUrl) => {
292
+ if (isFileSystemPath(importer)) {
293
+ importer = fileUrlConverter.asFileUrl(importer)
294
+ }
295
+ let url
296
+ if (specifier[0] === "/") {
297
+ url = new URL(specifier.slice(1), rootDirectoryUrl).href
298
+ } else {
299
+ url = new URL(specifier, importer).href
300
+ }
301
+ const existingImporter = urlImporters[url]
302
+ if (!existingImporter) {
303
+ urlImporters[url] = importer
304
+ }
305
+ if (!url.startsWith("file:")) {
306
+ return { id: url, external: true }
307
+ }
308
+ if (!importCanBeBundled(url)) {
309
+ return { id: url, external: true }
310
+ }
311
+ const urlInfo = urlGraph.getUrlInfo(url)
312
+ if (!urlInfo) {
313
+ // happen when excluded by urlAnalysis.include
314
+ return { id: url, external: true }
315
+ }
316
+ if (!urlInfo.shouldHandle) {
317
+ return { id: url, external: true }
318
+ }
319
+ const filePath = fileUrlConverter.asFilePath(url)
320
+ return filePath
321
+ },
322
+ async load(rollupId) {
323
+ const fileUrl = fileUrlConverter.asFileUrl(rollupId)
324
+ const urlInfo = urlGraph.getUrlInfo(fileUrl)
325
+ return {
326
+ code: urlInfo.content,
327
+ map:
328
+ (sourcemaps === "file" || sourcemaps === "inline") &&
329
+ urlInfo.sourcemap
330
+ ? sourcemapConverter.toFilePaths(urlInfo.sourcemap)
331
+ : null,
332
+ }
333
+ },
334
+ }
335
+ }
336
+
337
+ const applyRollupPlugins = async ({
338
+ rollup,
339
+ rollupPlugins,
340
+ rollupInput,
341
+ rollupOutput,
342
+ }) => {
343
+ if (!rollup) {
344
+ const rollupModule = await import("rollup")
345
+ rollup = rollupModule.rollup
346
+ }
347
+ const { importAssertions } = await import("acorn-import-assertions")
348
+ const rollupReturnValue = await rollup({
349
+ ...rollupInput,
350
+ plugins: rollupPlugins,
351
+ acornInjectPlugins: [
352
+ importAssertions,
353
+ ...(rollupInput.acornInjectPlugins || []),
354
+ ],
355
+ })
356
+ const rollupOutputArray = await rollupReturnValue.generate(rollupOutput)
357
+ return rollupOutputArray
358
+ }
359
+
360
+ const willBeInsideJsDirectory = ({
361
+ chunkInfo,
362
+ fileUrlConverter,
363
+ jsModuleUrlInfos,
364
+ }) => {
365
+ // if the chunk is generated dynamically by rollup
366
+ // for an entry point jsenv will put that file inside js/ directory
367
+ // if it's generated dynamically for a file already in js/ directory
368
+ // both will be inside the js/ directory
369
+ if (!chunkInfo.facadeModuleId) {
370
+ // generated by rollup
371
+ return true
372
+ }
373
+ const url = fileUrlConverter.asFileUrl(chunkInfo.facadeModuleId)
374
+ const jsModuleUrlInfo = jsModuleUrlInfos.find(
375
+ (jsModuleUrlInfo) => jsModuleUrlInfo.url === url,
376
+ )
377
+ if (!jsModuleUrlInfo) {
378
+ // generated by rollup
379
+ return true
380
+ }
381
+ if (!jsModuleUrlInfo.isEntryPoint) {
382
+ // not an entry point, jsenv will put it inside js/ directory
383
+ return true
384
+ }
385
+ return false
386
+ }
@@ -0,0 +1,21 @@
1
+ import { fileSystemPathToUrl, urlToFileSystemPath } from "@jsenv/urls"
2
+
3
+ export const fileUrlConverter = {
4
+ asFilePath: (fileUrl) => {
5
+ const filePath = urlToFileSystemPath(fileUrl)
6
+ const urlObject = new URL(fileUrl)
7
+ const { searchParams } = urlObject
8
+ return `${filePath}${stringifyQuery(searchParams)}`
9
+ },
10
+ asFileUrl: (filePath) => {
11
+ return decodeURIComponent(fileSystemPathToUrl(filePath)).replace(
12
+ /[=](?=&|$)/g,
13
+ "",
14
+ )
15
+ },
16
+ }
17
+
18
+ const stringifyQuery = (searchParams) => {
19
+ const search = searchParams.toString()
20
+ return search ? `?${search}` : ""
21
+ }
@@ -0,0 +1,43 @@
1
+ import { bundleCss } from "./css/bundle_css.js"
2
+ import { bundleJsClassicWorkers } from "./js_classic_workers/bundle_js_classic_workers.js"
3
+ import { bundleJsModules } from "./js_module/bundle_js_modules.js"
4
+
5
+ export const jsenvPluginBundling = ({
6
+ css = {},
7
+ js_classic = {},
8
+ js_module = {},
9
+ } = {}) => {
10
+ return {
11
+ name: "jsenv:bundling",
12
+ appliesDuring: "build",
13
+ bundle: {
14
+ css: css
15
+ ? (cssUrlInfos, context) => {
16
+ return bundleCss({
17
+ cssUrlInfos,
18
+ context,
19
+ options: css === true ? {} : css,
20
+ })
21
+ }
22
+ : undefined,
23
+ js_classic: js_classic
24
+ ? (jsClassicUrlInfos, context) => {
25
+ return bundleJsClassicWorkers({
26
+ jsClassicUrlInfos,
27
+ context,
28
+ options: js_classic === true ? {} : js_classic,
29
+ })
30
+ }
31
+ : undefined,
32
+ js_module: js_module
33
+ ? (jsModuleUrlInfos, context) => {
34
+ return bundleJsModules({
35
+ jsModuleUrlInfos,
36
+ context,
37
+ options: js_module === true ? {} : js_module,
38
+ })
39
+ }
40
+ : undefined,
41
+ },
42
+ }
43
+ }
package/src/main.js ADDED
@@ -0,0 +1,2 @@
1
+ export { jsenvPluginBundling } from "./jsenv_plugin_bundling.js"
2
+ export { bundleJsModules } from "./js_module/bundle_js_modules.js"