@corva/create-app 0.124.0 → 0.125.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.
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// @corva/dc-platform-shared once the standalone flow is proven, which is why
|
|
3
3
|
// nothing here imports from the app's own source.
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
5
|
-
import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
6
|
-
import { join, resolve } from 'node:path';
|
|
5
|
+
import { existsSync, readFileSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { join, relative, resolve } from 'node:path';
|
|
7
7
|
|
|
8
8
|
import dotenv from 'dotenv';
|
|
9
9
|
import { expand } from 'dotenv-expand';
|
|
@@ -100,10 +100,22 @@ const normalizeIifeGlobalAssignment = (jsContent, appIdentifier, emittedGlobalNa
|
|
|
100
100
|
return normalizedAssignment + jsContent;
|
|
101
101
|
};
|
|
102
102
|
|
|
103
|
+
// webpack's `output.publicPath: 'auto'` read the bundle's own <script> src once, at bundle
|
|
104
|
+
// init, and served every emitted asset from next to it. Same expression, same moment.
|
|
105
|
+
const ASSET_BASE_EXPR =
|
|
106
|
+
'(function(){var d=document,s=d.currentScript;if(!s||s.tagName.toUpperCase()!=="SCRIPT"){' +
|
|
107
|
+
'var a=d.getElementsByTagName("script");s=a[a.length-1];}' +
|
|
108
|
+
'return((s&&s.src)||d.baseURI).replace(/[^/]*(?:[?#].*)?$/,"");})()';
|
|
109
|
+
|
|
110
|
+
// url() in the extracted CSS carries this marker instead of a path (see renderBuiltUrl); the
|
|
111
|
+
// marker is spliced out into a concatenation with the base, so the injected stylesheet points
|
|
112
|
+
// at the CDN directory rather than at the host document's.
|
|
113
|
+
const ASSET_MARKER = '__CORVA_ASSET__';
|
|
114
|
+
|
|
103
115
|
const createCssInjection = cssContent =>
|
|
104
|
-
`(function(){var s=document.createElement('style');s.textContent=${JSON.stringify(
|
|
116
|
+
`(function(){var B=${ASSET_BASE_EXPR};var s=document.createElement('style');s.textContent=${JSON.stringify(
|
|
105
117
|
cssContent,
|
|
106
|
-
)};document.head.appendChild(s)})();`;
|
|
118
|
+
).replaceAll(ASSET_MARKER, '"+B+"')};document.head.appendChild(s)})();`;
|
|
107
119
|
|
|
108
120
|
// The platform loads a single file, so the emitted stylesheets are folded back into it.
|
|
109
121
|
const finalizeJsBundle = ({ outDir, cssFileNames, appIdentifier, emittedGlobalName }) => {
|
|
@@ -124,6 +136,107 @@ const finalizeJsBundle = ({ outDir, cssFileNames, appIdentifier, emittedGlobalNa
|
|
|
124
136
|
cssFileNames.forEach(fileName => unlinkSync(join(outDir, fileName)));
|
|
125
137
|
};
|
|
126
138
|
|
|
139
|
+
// webpack emitted binaries as files (`asset/resource`, publicPath resolved at runtime) and
|
|
140
|
+
// inlined only SVGs under svg-url-loader's 10 KB limit. Vite's library mode inlines every asset
|
|
141
|
+
// whatever `assetsInlineLimit` says — `?no-inline` is the one thing it still honours, so the
|
|
142
|
+
// same split is restored by tagging the id.
|
|
143
|
+
const EMITTED_ASSET_RE = /\.(png|jpe?g|gif|webp|avif|woff2?|eot|ttf|otf|mp4|webm|ogg|mp3|wav)$/i;
|
|
144
|
+
const SVG_INLINE_LIMIT = 10000;
|
|
145
|
+
|
|
146
|
+
const emitAssetsAsFilesPlugin = () => {
|
|
147
|
+
const resolving = new Set();
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
name: 'corva-emit-assets-as-files',
|
|
151
|
+
// before vite:resolve, which would otherwise resolve the asset id first and never let this
|
|
152
|
+
// hook see it
|
|
153
|
+
enforce: 'pre',
|
|
154
|
+
async resolveId(source, importer, options) {
|
|
155
|
+
if (source.includes('?') || (!EMITTED_ASSET_RE.test(source) && !/\.svg$/i.test(source))) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const key = `${importer}\u0000${source}`;
|
|
160
|
+
|
|
161
|
+
if (resolving.has(key)) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
resolving.add(key);
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const resolved = await this.resolve(source, importer, options);
|
|
169
|
+
|
|
170
|
+
if (!resolved || resolved.external) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (/\.svg$/i.test(resolved.id) && statSync(resolved.id).size < SVG_INLINE_LIMIT) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return `${resolved.id}?no-inline`;
|
|
179
|
+
} finally {
|
|
180
|
+
resolving.delete(key);
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// Every DC app on a dashboard renders into the same document, which is why webpack's css-loader
|
|
187
|
+
// ran with `modules` on for every stylesheet: nothing an app shipped could leak onto its
|
|
188
|
+
// neighbours. Vite decides by filename, so a plain `.css` would quietly become global. Refuse it
|
|
189
|
+
// instead — the selectors that really have to be global still go through `:global(...)`, exactly
|
|
190
|
+
// as they did under webpack.
|
|
191
|
+
const UNSCOPED_CSS_RE = /(?<!\.module)\.(css|scss|sass)(\?|$)/;
|
|
192
|
+
const STYLESHEET_IMPORTER_RE = /\.(css|scss|sass)(\?|$)/;
|
|
193
|
+
|
|
194
|
+
const rejectUnscopedCssPlugin = pwd => {
|
|
195
|
+
const resolving = new Set();
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
name: 'corva-reject-unscoped-css',
|
|
199
|
+
enforce: 'pre',
|
|
200
|
+
async resolveId(source, importer, options) {
|
|
201
|
+
// no importer: an entry. Stylesheet importer: a sass/postcss partial, which is not a module
|
|
202
|
+
// in either build system.
|
|
203
|
+
if (!importer || !UNSCOPED_CSS_RE.test(source) || STYLESHEET_IMPORTER_RE.test(importer)) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const key = `${importer}\u0000${source}`;
|
|
208
|
+
|
|
209
|
+
if (resolving.has(key)) {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
resolving.add(key);
|
|
214
|
+
|
|
215
|
+
let resolved;
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
resolved = await this.resolve(source, importer, options);
|
|
219
|
+
} finally {
|
|
220
|
+
resolving.delete(key);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// a dependency's own stylesheet is its business, and it was never scoped to this app
|
|
224
|
+
if (!resolved || resolved.external || resolved.id.includes('node_modules')) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
throw new Error(
|
|
229
|
+
`${relative(pwd, resolved.id)} is imported as a plain stylesheet by ${relative(pwd, importer)}.\n` +
|
|
230
|
+
'Please use *.module.css — global styles may leak into other platform elements, and ' +
|
|
231
|
+
'that surfaces later as weird bugs nobody traces back to this file.\n' +
|
|
232
|
+
'If you really do want a global style and know what you are doing, keep the .module.css ' +
|
|
233
|
+
'file and wrap that one selector:\n' +
|
|
234
|
+
' :global(.my-global-class) { color: red; }',
|
|
235
|
+
);
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
};
|
|
239
|
+
|
|
127
240
|
const getCorvaApiEnvVariables = () => {
|
|
128
241
|
const apiEnv = process.env.CORVA_API_ENV || CORVA_API_ENVS.production;
|
|
129
242
|
|
|
@@ -233,7 +346,12 @@ export const createViteConfig = ({ pwd, overrides = {} }) => {
|
|
|
233
346
|
return `this["${dep}"].default || this["${dep}"]`;
|
|
234
347
|
}
|
|
235
348
|
|
|
236
|
-
return `this["${dep}"]${id
|
|
349
|
+
return `this["${dep}"]${id
|
|
350
|
+
.slice(dep.length)
|
|
351
|
+
.split('/')
|
|
352
|
+
.filter(Boolean)
|
|
353
|
+
.map(segment => `.${segment}`)
|
|
354
|
+
.join('')}`;
|
|
237
355
|
};
|
|
238
356
|
|
|
239
357
|
const getAlias = () => {
|
|
@@ -279,10 +397,14 @@ export const createViteConfig = ({ pwd, overrides = {} }) => {
|
|
|
279
397
|
},
|
|
280
398
|
css: {
|
|
281
399
|
modules: { localsConvention: 'camelCase' },
|
|
282
|
-
postcss
|
|
400
|
+
// webpack's postcss-loader ran preset-env with no options, i.e. stage 2; a narrower
|
|
401
|
+
// stage silently stops transpiling CSS features the app already relies on
|
|
402
|
+
postcss: { plugins: [presetEnv()] },
|
|
283
403
|
},
|
|
284
|
-
plugins: [react(), dotEnvEndpointPlugin(pwd)],
|
|
285
|
-
resolve:
|
|
404
|
+
plugins: [react(), dotEnvEndpointPlugin(pwd), rejectUnscopedCssPlugin(pwd)],
|
|
405
|
+
// webpack ran with `resolve.symlinks: false`; a yarn-linked @corva/ui has to keep
|
|
406
|
+
// resolving through the link, not through its real path
|
|
407
|
+
resolve: { alias: getAlias(), preserveSymlinks: true },
|
|
286
408
|
test: {
|
|
287
409
|
watch: false,
|
|
288
410
|
globals: true,
|
|
@@ -334,11 +456,20 @@ export const createViteConfig = ({ pwd, overrides = {} }) => {
|
|
|
334
456
|
return {
|
|
335
457
|
...config,
|
|
336
458
|
define: {
|
|
459
|
+
// webpack's prod config ran `EnvironmentPlugin(process.env)`: one textual replacement
|
|
460
|
+
// per key, so only the keys the source actually reads reach the bundle. Without it an
|
|
461
|
+
// app reading e.g. process.env.CORVA_API_ENV silently compiles to undefined.
|
|
462
|
+
...Object.fromEntries(
|
|
463
|
+
Object.entries(process.env)
|
|
464
|
+
.filter(([key]) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key))
|
|
465
|
+
.map(([key, value]) => [`process.env.${key}`, JSON.stringify(value)]),
|
|
466
|
+
),
|
|
337
467
|
'process.env': { NODE_ENV: 'production' },
|
|
338
468
|
'process.env.NODE_ENV': '"production"',
|
|
339
469
|
},
|
|
340
470
|
plugins: [
|
|
341
471
|
...config.plugins,
|
|
472
|
+
emitAssetsAsFilesPlugin(),
|
|
342
473
|
viteStaticCopy({ targets: [{ src: 'manifest.json', dest: '.' }] }),
|
|
343
474
|
{
|
|
344
475
|
name: 'corva-single-file-bundle',
|
|
@@ -352,6 +483,14 @@ export const createViteConfig = ({ pwd, overrides = {} }) => {
|
|
|
352
483
|
},
|
|
353
484
|
},
|
|
354
485
|
],
|
|
486
|
+
// an asset URL has to be resolved at runtime, from the bundle's own location: the same
|
|
487
|
+
// app.js is served from a different package directory for every version
|
|
488
|
+
experimental: {
|
|
489
|
+
renderBuiltUrl: (filename, { hostType }) =>
|
|
490
|
+
(hostType === 'css'
|
|
491
|
+
? `${ASSET_MARKER}${filename}`
|
|
492
|
+
: { runtime: `__CORVA_ASSET_BASE__+${JSON.stringify(filename)}` }),
|
|
493
|
+
},
|
|
355
494
|
build: {
|
|
356
495
|
outDir: 'dist',
|
|
357
496
|
emptyOutDir: true,
|
|
@@ -360,7 +499,6 @@ export const createViteConfig = ({ pwd, overrides = {} }) => {
|
|
|
360
499
|
cssCodeSplit: false,
|
|
361
500
|
minify: 'terser',
|
|
362
501
|
terserOptions: {
|
|
363
|
-
compress: { drop_console: true, drop_debugger: true },
|
|
364
502
|
format: { comments: false },
|
|
365
503
|
},
|
|
366
504
|
// lib mode plus named exports keeps the IIFE compatible with the platform
|
|
@@ -378,13 +516,16 @@ export const createViteConfig = ({ pwd, overrides = {} }) => {
|
|
|
378
516
|
format: 'iife',
|
|
379
517
|
name: appIifeName,
|
|
380
518
|
entryFileNames: 'app.js',
|
|
519
|
+
// flat next to app.js, the way webpack's default assetModuleFilename emitted them
|
|
520
|
+
assetFileNames: '[hash][extname]',
|
|
381
521
|
globals: toGlobal,
|
|
382
522
|
exports: 'named',
|
|
383
523
|
// Rolldown doesn't rewrite `require()` inside bundled CJS modules (e.g.
|
|
384
524
|
// react-is) to their external global references. This shim resolves them
|
|
385
525
|
// to the globals the IIFE receives as params.
|
|
386
526
|
intro:
|
|
387
|
-
|
|
527
|
+
`var require=function(id){var g=globalThis[id];return g&&g.default||g;};` +
|
|
528
|
+
`var __CORVA_ASSET_BASE__=${ASSET_BASE_EXPR};`,
|
|
388
529
|
},
|
|
389
530
|
plugins: [
|
|
390
531
|
{
|
package/package.json
CHANGED