@corva/dc-platform-shared 0.0.0-d06d228 → 0.0.0-d3af965
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 +1 -1
- package/vite/corva-vite-config.mjs +53 -64
- package/vite/unscoped-css.mjs +63 -0
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"@corva/dc-platform-shared","version":"0.0.0-
|
|
1
|
+
{"name":"@corva/dc-platform-shared","version":"0.0.0-d3af965","description":"A library to share internal DC platform specific code","main":"./dist","module":"./src/index.js","repository":{"type":"git","url":"https://github.com/corva-ai/corva-dc-platform-shared"},"author":"Oleksandr Krupko <oleksandr.krupko@corva.ai>","license":"MIT","//sass-alias":"sass-embedded installed under the name 'sass' (see dependencies) because rollup-plugin-postcss@4.0.2 hard-requires require('sass') with no override hook (see rollup.config.js). Drop this alias when rollup-plugin-postcss is replaced.","scripts":{"helper-cli":"npx @corva/fe-dev-helper-cli@latest","lint":"eslint --cache ./src/","lint-staged":"lint-staged","build":"./scripts/build.sh production","build-watch":"./scripts/build.sh development -w","get-changelog":"conventional-changelog -r 2 -p angular","release":"git fetch --tags && git add -A && standard-version -a","generate-css-themes":"echo 'skip: css themes generated by @corva/ui build'","start":"yarn generate-css-themes && cross-env NODE_ENV=local rollup -c -w","test":"jest","test-coverage":"jest --watchAll --coverage && http-serve coverage","test-watch":"jest --watch"},"lint-staged":{"*.js":["eslint --fix"]},"standard-version":{"skip":{"tag":true}},"jest":{"collectCoverageFrom":["./**/*.{js,jsx,mjs}"],"coverageReporters":["html","text-summary"],"moduleNameMapper":{"~(.*)":"<rootDir>/src/$1","\\.(css|less|scss|sass)$":"identity-obj-proxy"},"setupFiles":["<rootDir>/jest/setEnvVars.js"],"testMatch":["**/__tests__/**/?(*.)+(spec|test).[jt]s?(x)"],"transform":{"^.+\\.jsx?$":"babel-jest","^.+\\.mjs$":"babel-jest","^.+\\.svg$":"<rootDir>/jest/svgTransform.js"},"transformIgnorePatterns":["/node_modules/(?!@corva/ui|@icon-park/react)"],"moduleFileExtensions":["js","jsx","json","mjs","node"],"testEnvironment":"jsdom"},"dependencies":{"@babel/core":"^7.12.10","@babel/plugin-transform-react-jsx":"^7.12.11","@babel/plugin-transform-runtime":"^7.12.10","@babel/preset-env":"^7.12.11","@babel/preset-react":"^7.12.10","@corva/eslint-config-browser":"^0.0.4","@date-io/moment":"1.3.13","@icon-park/react":"1.4.2","@material-ui/core":"4.11.2","@material-ui/icons":"4.9.1","@material-ui/lab":"4.0.0-alpha.57","@material-ui/pickers":"3.2.10","@pmmmwh/react-refresh-webpack-plugin":"0.5.10","@vitejs/plugin-react":"6.1.1","@vitest/coverage-v8":"4.1.11","babel-eslint":"10.0.3","babel-jest":"^30.2.0","babel-loader":"^10.0.0","babel-plugin-file-loader":"^2.0.0","babel-plugin-root-import":"^6.5.0","chalk":"4.1.1","classnames":"2.2.6","clean-webpack-plugin":"3.0.0","copy-webpack-plugin":"^13.0.1","css-loader":"^7.1.0","dotenv":"16.4.7","dotenv-expand":"11.0.7","eslint-config-airbnb":"17.1.0","eslint-plugin-jest-dom":"3.9.4","eslint-plugin-prettier":"3.1.1","eslint-plugin-testing-library":"^3.9.2","express":"4.21.2","fs":"0.0.1-security","html-webpack-plugin":"^5.6.4","identity-obj-proxy":"^3.0.0","jest":"^30.2.0","jest-environment-jsdom":"^30.2.0","jsdom":"22.1.0","jsona":"1.5.1","lodash":"4.18.1","moment":"2.29.4","node-fetch":"^2.6.1","path":"0.12.7","postcss":"^8.5.26","postcss-import":"12.0.1","postcss-loader":"^8.1.0","postcss-preset-env":"^11.3.0","prettier":"^2.1.2","process":"^0.11.10","prop-types":"15.7.2","react":"18.3.1","react-dom":"18.3.1","react-grid-layout":"1.3.4","react-router-dom":"^5.3.4","react-sizeme":"2.5.2","rollup-plugin-copy":"^3.4.0","rollup-plugin-license":"3.6.0","sass":"npm:sass-embedded@^1.99.0","sass-loader":"^16.0.5","semver":"7.5.2","style-loader":"^4.0.0","svg-url-loader":"^8.1.0","terser-webpack-plugin":"^5.3.14","uuid":"3.4.0","vite":"8.3.0","vite-plugin-static-copy":"4.1.0","vitest":"4.1.11","webpack":"5.94.0","webpack-cli":"5.1.4","webpack-dev-server":"^5.2.2","webpack-merge":"5.9.0"},"peerDependencies":{"@corva/ui":"*"},"devDependencies":{"@corva/ui":"3.78.0-2","@rollup/plugin-babel":"^5.2.1","@rollup/plugin-commonjs":"^15.0.0","@rollup/plugin-image":"^2.0.6","@rollup/plugin-json":"^4.1.0","@rollup/plugin-node-resolve":"^9.0.0","@testing-library/jest-dom":"5.16.5","@testing-library/react":"^14.0.0","babel-plugin-external-helpers":"^6.22.0","conventional-changelog-cli":"^2.1.0","cross-env":"^7.0.3","git-conventional-commits":"^1.1.0","lint-staged":"^10.5.4","postcss-url":"9.0.0","pre-commit":"1.2.2","rollup":"^2.28.2","rollup-plugin-cleanup":"^3.2.1","rollup-plugin-css-bundle":"^1.0.4","rollup-plugin-css-only":"^2.1.0","rollup-plugin-node-externals":"^2.2.0","rollup-plugin-postcss":"^4.0.2","rollup-plugin-rename-node-modules":"^1.0.1","rollup-plugin-terser":"^7.0.2","rollup-plugin-visualizer":"^4.1.1","standard-version":"^9.0.0"},"engines":{"node":">=24.15.0"},"resolutions":{"braces":"3.0.3","@babel/runtime":"^7.18.0","lodash":"4.18.1"}}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { randomUUID } from 'node:crypto';
|
|
5
5
|
import { builtinModules, createRequire } from 'node:module';
|
|
6
6
|
import { existsSync, readdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
7
|
-
import {
|
|
7
|
+
import { join, relative, resolve } from 'node:path';
|
|
8
8
|
|
|
9
9
|
import dotenv from 'dotenv';
|
|
10
10
|
import { expand } from 'dotenv-expand';
|
|
@@ -14,6 +14,8 @@ import { viteStaticCopy } from 'vite-plugin-static-copy';
|
|
|
14
14
|
import presetEnv from 'postcss-preset-env';
|
|
15
15
|
import { defineConfig, mergeConfig } from 'vitest/config';
|
|
16
16
|
|
|
17
|
+
import { rejectUnscopedCssPlugin } from './unscoped-css.mjs';
|
|
18
|
+
|
|
17
19
|
const require = createRequire(import.meta.url);
|
|
18
20
|
|
|
19
21
|
const {
|
|
@@ -145,9 +147,29 @@ const finalizeJsBundle = ({ outDir, cssFileNames, appIdentifier, emittedGlobalNa
|
|
|
145
147
|
// inlined only SVGs under svg-url-loader's 10 KB limit. Vite's library mode inlines every asset
|
|
146
148
|
// whatever `assetsInlineLimit` says — `?no-inline` is the one thing it still honours, so the
|
|
147
149
|
// same split is restored by tagging the id.
|
|
148
|
-
const
|
|
150
|
+
const EMITTED_ASSET_EXTENSIONS =
|
|
151
|
+
'png|jpe?g|gif|webp|avif|woff2?|eot|ttf|otf|mp4|webm|ogg|mp3|wav|glb|gltf|wasm';
|
|
152
|
+
const EMITTED_ASSET_RE = new RegExp(`\\.(${EMITTED_ASSET_EXTENSIONS})$`, 'i');
|
|
153
|
+
// `new URL('./x.wasm', import.meta.url)` names an asset without importing it, so the id never
|
|
154
|
+
// reaches resolveId; the tag has to be written into the literal before vite:asset reads it.
|
|
155
|
+
const NEW_URL_ASSET_RE = new RegExp(
|
|
156
|
+
`(new\\s+URL\\(\\s*)(['"])([^'"?]+\\.(?:${EMITTED_ASSET_EXTENSIONS}))(\\2\\s*,\\s*import\\.meta\\.url)`,
|
|
157
|
+
'gi',
|
|
158
|
+
);
|
|
149
159
|
const SVG_INLINE_LIMIT = 10000;
|
|
150
160
|
|
|
161
|
+
const tagNewUrlAssetsPlugin = () => ({
|
|
162
|
+
name: 'corva-tag-new-url-assets',
|
|
163
|
+
enforce: 'pre',
|
|
164
|
+
transform(code) {
|
|
165
|
+
if (!code.includes('import.meta.url')) return null;
|
|
166
|
+
|
|
167
|
+
const tagged = code.replace(NEW_URL_ASSET_RE, '$1$2$3?no-inline$4');
|
|
168
|
+
|
|
169
|
+
return tagged === code ? null : { code: tagged, map: null };
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
|
|
151
173
|
const emitAssetsAsFilesPlugin = () => {
|
|
152
174
|
const resolving = new Set();
|
|
153
175
|
|
|
@@ -188,64 +210,6 @@ const emitAssetsAsFilesPlugin = () => {
|
|
|
188
210
|
};
|
|
189
211
|
};
|
|
190
212
|
|
|
191
|
-
// Every DC app on a dashboard renders into the same document, which is why webpack's css-loader
|
|
192
|
-
// ran with `modules` on for every stylesheet: nothing an app shipped could leak onto its
|
|
193
|
-
// neighbours. Vite decides by filename, so a plain `.css` would quietly become global. Refuse it
|
|
194
|
-
// instead, and give the genuinely global ones one spelling: `*.globals.css`. That extension stays
|
|
195
|
-
// a plain stylesheet, which is what keeps it in the bundle — a `.module` file imported for its
|
|
196
|
-
// side effect alone is treeshaken away, with its scoped rules as well as its `:global()` ones.
|
|
197
|
-
const UNSCOPED_CSS_RE = /(?<!\.module)(?<!\.globals)\.(css|scss|sass)(\?|$)/;
|
|
198
|
-
const STYLESHEET_IMPORTER_RE = /\.(css|scss|sass)(\?|$)/;
|
|
199
|
-
|
|
200
|
-
const rejectUnscopedCssPlugin = pwd => {
|
|
201
|
-
const resolving = new Set();
|
|
202
|
-
|
|
203
|
-
return {
|
|
204
|
-
name: 'corva-reject-unscoped-css',
|
|
205
|
-
enforce: 'pre',
|
|
206
|
-
async resolveId(source, importer, options) {
|
|
207
|
-
// no importer: an entry. Stylesheet importer: a sass/postcss partial, which is not a module
|
|
208
|
-
// in either build system.
|
|
209
|
-
if (!importer || !UNSCOPED_CSS_RE.test(source) || STYLESHEET_IMPORTER_RE.test(importer)) {
|
|
210
|
-
return null;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const key = `${importer}\u0000${source}`;
|
|
214
|
-
|
|
215
|
-
if (resolving.has(key)) {
|
|
216
|
-
return null;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
resolving.add(key);
|
|
220
|
-
|
|
221
|
-
let resolved;
|
|
222
|
-
|
|
223
|
-
try {
|
|
224
|
-
resolved = await this.resolve(source, importer, options);
|
|
225
|
-
} finally {
|
|
226
|
-
resolving.delete(key);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// a dependency's own stylesheet is its business, and it was never scoped to this app
|
|
230
|
-
if (!resolved || resolved.external || resolved.id.includes('node_modules')) {
|
|
231
|
-
return null;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
throw new Error(
|
|
235
|
-
`${relative(pwd, resolved.id)} is imported as a plain stylesheet by ${relative(pwd, importer)}.\n` +
|
|
236
|
-
'Please use *.module.css — global styles may leak into other platform elements, and ' +
|
|
237
|
-
'that surfaces later as weird bugs nobody traces back to this file.\n' +
|
|
238
|
-
'When the styles genuinely have to be global — markup this app does not render, or class ' +
|
|
239
|
-
'names another module reaches with querySelector — rename the file to ' +
|
|
240
|
-
`*.globals${extname(resolved.id)} and keep importing it for its side effect.\n` +
|
|
241
|
-
'Do not reach for a .module file with :global() instead: a module stylesheet imported ' +
|
|
242
|
-
'for its side effect alone is treeshaken out of the bundle, styles and all, and the ' +
|
|
243
|
-
'build stays green.',
|
|
244
|
-
);
|
|
245
|
-
},
|
|
246
|
-
};
|
|
247
|
-
};
|
|
248
|
-
|
|
249
213
|
const getCorvaApiEnvVariables = () => {
|
|
250
214
|
const apiEnv = process.env.CORVA_API_ENV || CORVA_API_ENVS.production;
|
|
251
215
|
|
|
@@ -325,7 +289,16 @@ const show = error => {
|
|
|
325
289
|
);
|
|
326
290
|
};
|
|
327
291
|
|
|
328
|
-
|
|
292
|
+
// Browsers fire this when a ResizeObserver callback schedules another layout pass. It is a
|
|
293
|
+
// notice, not a failure, and webpack-dev-server's overlay filtered it out too; showing it puts a
|
|
294
|
+
// full-screen overlay over a working app.
|
|
295
|
+
const isBenign = error => /ResizeObserver loop/.test(error?.message ?? String(error ?? ''));
|
|
296
|
+
|
|
297
|
+
window.addEventListener('error', event => {
|
|
298
|
+
const error = event.error ?? event.message;
|
|
299
|
+
|
|
300
|
+
if (!isBenign(error)) show(error);
|
|
301
|
+
});
|
|
329
302
|
|
|
330
303
|
window.addEventListener('unhandledrejection', event => {
|
|
331
304
|
const authRecovery = AuthRecovery.getInstance();
|
|
@@ -335,7 +308,7 @@ window.addEventListener('unhandledrejection', event => {
|
|
|
335
308
|
return;
|
|
336
309
|
}
|
|
337
310
|
|
|
338
|
-
show(event.reason);
|
|
311
|
+
if (!isBenign(event.reason)) show(event.reason);
|
|
339
312
|
});
|
|
340
313
|
`;
|
|
341
314
|
|
|
@@ -513,12 +486,19 @@ const scanAppImports = pwd => {
|
|
|
513
486
|
// ("optimized dependencies changed. reloading"). Listing them up front makes the first load one
|
|
514
487
|
// pass instead of two — 4.6 s to a rendered app becomes 2.0 s on a 169-file app, cold cache.
|
|
515
488
|
// Interop does not matter here the way it does for tests, so this list is everything importable.
|
|
489
|
+
// This package's own CJS entry is `getWebpackConfig`, a Node-side build tool. An app that keeps a
|
|
490
|
+
// webpack config under `src/` (dashflow's embedded submodule) pulls it into the specifier scan, and
|
|
491
|
+
// pre-bundling it drags webpack and vite in behind it — vite then reads `lightningcss/package.json`,
|
|
492
|
+
// which lightningcss does not export, and the optimizer dies before the server or the first test.
|
|
493
|
+
const isOwnBuildTool = specifier => specifier.startsWith('@corva/dc-platform-shared');
|
|
494
|
+
|
|
516
495
|
const getDevOptimizeDeps = (pwd, { specifiers }) => {
|
|
517
496
|
const appRequire = createRequire(join(pwd, 'package.json'));
|
|
518
497
|
|
|
519
498
|
return specifiers
|
|
520
499
|
.filter(specifier => /^@?[\w.-]/.test(specifier) && !specifier.startsWith('@/'))
|
|
521
500
|
.filter(specifier => !builtinModules.includes(specifier.replace(/^node:/, '')))
|
|
501
|
+
.filter(specifier => !isOwnBuildTool(specifier))
|
|
522
502
|
.filter(specifier => hasEntryFile(pwd, appRequire, specifier))
|
|
523
503
|
.sort();
|
|
524
504
|
};
|
|
@@ -530,6 +510,7 @@ const getTestOptimizeDeps = (pwd, { specifiers, spied }) => {
|
|
|
530
510
|
.filter(specifier => /^@?[\w.-]/.test(specifier) && !specifier.startsWith('@/'))
|
|
531
511
|
.filter(specifier => !builtinModules.includes(specifier.replace(/^node:/, '')))
|
|
532
512
|
.filter(specifier => !TEST_EXTERNAL_DEP_RE.test(specifier))
|
|
513
|
+
.filter(specifier => !isOwnBuildTool(specifier))
|
|
533
514
|
.filter(specifier => !spied.has(specifier))
|
|
534
515
|
.filter(specifier => isEsmPackage(pwd, specifier) && hasEntryFile(pwd, appRequire, specifier))
|
|
535
516
|
.sort();
|
|
@@ -648,6 +629,9 @@ export const createViteConfig = ({ pwd, overrides = {} }) => {
|
|
|
648
629
|
return defaultAlias;
|
|
649
630
|
}
|
|
650
631
|
|
|
632
|
+
// tsc resolves every `paths` value against `baseUrl`, not against the tsconfig's own directory
|
|
633
|
+
const pathsRoot = resolve(pwd, compilerOptions?.baseUrl || '.');
|
|
634
|
+
|
|
651
635
|
return {
|
|
652
636
|
...defaultAlias,
|
|
653
637
|
...Object.fromEntries(
|
|
@@ -660,8 +644,8 @@ export const createViteConfig = ({ pwd, overrides = {} }) => {
|
|
|
660
644
|
key.endsWith('/*')
|
|
661
645
|
// keep the `/*` boundary: a bare `@` prefix-matches @types/react, @corva/ui and
|
|
662
646
|
// every other scoped package and rewrites them into src/
|
|
663
|
-
? [`${key.slice(0, -1)}`, `${resolve(
|
|
664
|
-
: [key, resolve(
|
|
647
|
+
? [`${key.slice(0, -1)}`, `${resolve(pathsRoot, value.replace('/*', ''))}/`]
|
|
648
|
+
: [key, resolve(pathsRoot, value)]
|
|
665
649
|
)),
|
|
666
650
|
),
|
|
667
651
|
};
|
|
@@ -802,9 +786,14 @@ export const createViteConfig = ({ pwd, overrides = {} }) => {
|
|
|
802
786
|
'process.env': { NODE_ENV: 'production' },
|
|
803
787
|
'process.env.NODE_ENV': '"production"',
|
|
804
788
|
global: 'globalThis',
|
|
789
|
+
// an iife has no import.meta, and rolldown turns every read of it into `{}` — which
|
|
790
|
+
// makes `new URL(asset, '' + import.meta.url)` throw on an "undefined" base. The
|
|
791
|
+
// bundle's own location is what webpack handed modules here.
|
|
792
|
+
'import.meta.url': '__CORVA_ASSET_BASE__',
|
|
805
793
|
},
|
|
806
794
|
plugins: [
|
|
807
795
|
...config.plugins,
|
|
796
|
+
tagNewUrlAssetsPlugin(),
|
|
808
797
|
emitAssetsAsFilesPlugin(),
|
|
809
798
|
viteStaticCopy({ targets: [{ src: 'manifest.json', dest: '.' }] }),
|
|
810
799
|
{
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { extname, relative } from 'node:path';
|
|
2
|
+
|
|
3
|
+
// Every DC app on a dashboard renders into the same document, which is why webpack's css-loader
|
|
4
|
+
// ran with `modules` on for every stylesheet: nothing an app shipped could leak onto its
|
|
5
|
+
// neighbours. Vite decides by filename, so a plain `.css` would quietly become global. Refuse it
|
|
6
|
+
// instead, and give the genuinely global ones one spelling: `globals.css` or `<name>.globals.css`.
|
|
7
|
+
// That extension stays a plain stylesheet, which is what keeps it in the bundle — a `.module` file
|
|
8
|
+
// imported for its side effect ALONE is treeshaken away, rules and all.
|
|
9
|
+
export const UNSCOPED_CSS_RE = /(?<!\.module)(?<![./]globals)\.(css|scss|sass)(\?|$)/;
|
|
10
|
+
export const STYLESHEET_IMPORTER_RE = /\.(css|scss|sass)(\?|$)/;
|
|
11
|
+
|
|
12
|
+
export const rejectUnscopedCssPlugin = pwd => {
|
|
13
|
+
const resolving = new Set();
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
name: 'corva-reject-unscoped-css',
|
|
17
|
+
enforce: 'pre',
|
|
18
|
+
async resolveId(source, importer, options) {
|
|
19
|
+
// no importer: an entry. Stylesheet importer: a sass/postcss partial, which is not a module
|
|
20
|
+
// in either build system.
|
|
21
|
+
if (!importer || !UNSCOPED_CSS_RE.test(source) || STYLESHEET_IMPORTER_RE.test(importer)) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const key = `${importer}\u0000${source}`;
|
|
26
|
+
|
|
27
|
+
if (resolving.has(key)) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
resolving.add(key);
|
|
32
|
+
|
|
33
|
+
let resolved;
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
resolved = await this.resolve(source, importer, options);
|
|
37
|
+
} finally {
|
|
38
|
+
resolving.delete(key);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// a dependency's own stylesheet is its business, and it was never scoped to this app
|
|
42
|
+
if (!resolved || resolved.external || resolved.id.includes('node_modules')) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const ext = extname(resolved.id.split('?')[0]);
|
|
47
|
+
|
|
48
|
+
throw new Error(
|
|
49
|
+
`${relative(pwd, resolved.id)} is imported as a plain stylesheet by ${relative(pwd, importer)}.\n` +
|
|
50
|
+
'Please use *.module.css — global styles may leak into other platform elements, and ' +
|
|
51
|
+
'that surfaces later as weird bugs nobody traces back to this file.\n' +
|
|
52
|
+
'When a class name genuinely has to stay unscoped:\n' +
|
|
53
|
+
'1. If the markup is rendered by this app, keep the file a module you already import ' +
|
|
54
|
+
'for its classes and nest the selector under a scoped parent: ' +
|
|
55
|
+
'`.chart :global(.outer-circle) { … }`. Only a module imported for its side effect ' +
|
|
56
|
+
'alone is treeshaken out.\n' +
|
|
57
|
+
`2. For markup outside the app's root — portals, tooltips, third-party DOM — rename the ` +
|
|
58
|
+
`file to globals${ext} or <name>.globals${ext} and keep importing it for its side effect.\n` +
|
|
59
|
+
'3. In a `.globals` file, prefix every selector with an app-specific class.',
|
|
60
|
+
);
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
};
|