tungsten 0.2.17 → 0.2.18

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.
@@ -22,13 +22,15 @@ describe('_client/cjsHelpers loadDeps()', ({
22
22
  it(`loads a dependency cycle instead of hanging`, async () => {
23
23
  const dir = await mkdtemp(join(tmpdir(), 'tungsten-load-deps-'));
24
24
  try {
25
- const a = pathToFileURL(join(dir, 'a.js')).href;
26
- const b = pathToFileURL(join(dir, 'b.js')).href;
27
- const c = pathToFileURL(join(dir, 'c.js')).href;
25
+ // .mjs so that node does not have to guess the module type of a file in a
26
+ // temp dir with no package.json
27
+ const a = pathToFileURL(join(dir, 'a.mjs')).href;
28
+ const b = pathToFileURL(join(dir, 'b.mjs')).href;
29
+ const c = pathToFileURL(join(dir, 'c.mjs')).href;
28
30
  // a <-> b is circular, and both reach c
29
- await writeFile(join(dir, 'a.js'), moduleSource([b, c]));
30
- await writeFile(join(dir, 'b.js'), moduleSource([a, c]));
31
- await writeFile(join(dir, 'c.js'), moduleSource([]));
31
+ await writeFile(join(dir, 'a.mjs'), moduleSource([b, c]));
32
+ await writeFile(join(dir, 'b.mjs'), moduleSource([a, c]));
33
+ await writeFile(join(dir, 'c.mjs'), moduleSource([]));
32
34
  const timeout = new Promise(resolve => setTimeout(() => resolve('timed out'), 5000).unref());
33
35
  const loaded = await Promise.race([loadDeps([a]), timeout]);
34
36
  expect(loaded).not.to.equal('timed out');
@@ -0,0 +1,9 @@
1
+ import { NodePath } from '@babel/core';
2
+ import type { ConditionalExpression, IfStatement } from '@babel/types';
3
+ export declare function MakeBabelTransformRemoveDevBlocks(): () => {
4
+ visitor: {
5
+ IfStatement(path: NodePath<IfStatement>): void;
6
+ ConditionalExpression(path: NodePath<ConditionalExpression>): void;
7
+ };
8
+ };
9
+ //# sourceMappingURL=BabelTransformRemoveDevBlocks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BabelTransformRemoveDevBlocks.d.ts","sourceRoot":"","sources":["../../src/babelPlugins/BabelTransformRemoveDevBlocks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,KAAK,EAAE,qBAAqB,EAAc,WAAW,EAAE,MAAM,cAAc,CAAA;AAOlF,wBAAgB,iCAAiC;;0BAef,QAAQ,CAAC,WAAW,CAAC;oCAUX,QAAQ,CAAC,qBAAqB,CAAC;;EAS1E"}
@@ -0,0 +1,38 @@
1
+ // Removes dev only branches from the production client bundle.
2
+ // `isDev` is false in production, so `if (isDev) {...}` blocks and `isDev ? a : b`
3
+ // expressions collapse to their alternate (or disappear entirely).
4
+ // This also lets rollup tree shake modules that are only imported by dev branches.
5
+ export function MakeBabelTransformRemoveDevBlocks() {
6
+ return function BabelTransformRemoveDevBlocks(/*babel*/
7
+ ) {
8
+ function investigationCondition(test) {
9
+ if (test.type === 'Identifier' && test.name === 'isDev') {
10
+ return true;
11
+ } else if (test.type === 'LogicalExpression' && test.operator === '&&') {
12
+ // false && anything is false
13
+ return investigationCondition(test.left) || investigationCondition(test.right);
14
+ }
15
+ return false;
16
+ }
17
+ return {
18
+ visitor: {
19
+ IfStatement(path) {
20
+ const test = path.node.test;
21
+ if (investigationCondition(test)) {
22
+ if (path.node.alternate) {
23
+ path.replaceWith(path.node.alternate);
24
+ } else {
25
+ path.remove();
26
+ }
27
+ }
28
+ },
29
+ ConditionalExpression(path) {
30
+ const test = path.node.test;
31
+ if (investigationCondition(test)) {
32
+ path.replaceWith(path.node.alternate);
33
+ }
34
+ }
35
+ }
36
+ };
37
+ };
38
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"rollup.user.prod.config.d.ts","sourceRoot":"","sources":["../../src/build/rollup.user.prod.config.ts"],"names":[],"mappings":"AAaA,KAAK,YAAY,GAAG;IAChB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CACrB,CAAA;AAeD,QAAA,MAAM,YAAY,EAAE,YAkBnB,CAAA;AAED,eAAe,YAAY,CAAA"}
1
+ {"version":3,"file":"rollup.user.prod.config.d.ts","sourceRoot":"","sources":["../../src/build/rollup.user.prod.config.ts"],"names":[],"mappings":"AAcA,KAAK,YAAY,GAAG;IAChB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CACrB,CAAA;AAeD,QAAA,MAAM,YAAY,EAAE,YAkBnB,CAAA;AAED,eAAe,YAAY,CAAA"}
@@ -6,6 +6,7 @@ import { terser } from "rollup-plugin-terser";
6
6
  //import gzip from "rollup-plugin-gzip"
7
7
  import jsonModule from "@rollup/plugin-json";
8
8
  import { MakeBabelTransformRemoveServerBlocks } from "../babelPlugins/BabelTransformRemoveServerBlocks.js";
9
+ import { MakeBabelTransformRemoveDevBlocks } from "../babelPlugins/BabelTransformRemoveDevBlocks.js";
9
10
  const json = typeof jsonModule === "function" ? jsonModule : jsonModule.default;
10
11
  function replaceProductionNodeEnv() {
11
12
  return {
@@ -25,7 +26,7 @@ const rollupConfig = {
25
26
  replaceProductionNodeEnv(), babel({
26
27
  babelHelpers: 'bundled',
27
28
  exclude: '**/node_modules/**',
28
- plugins: [MakeBabelTransformRemoveServerBlocks()]
29
+ plugins: [MakeBabelTransformRemoveServerBlocks(), MakeBabelTransformRemoveDevBlocks()]
29
30
  }),
30
31
  // must be after commonjs
31
32
  terser({
@@ -1 +1 @@
1
- {"version":3,"file":"styleContentSxy.d.ts","sourceRoot":"","sources":["../../src/style/styleContentSxy.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC9C,OAAO,EAAE,KAAK,WAAW,EAAsF,MAAM,mBAAmB,CAAA;AAWxI,wBAAgB,eAAe,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,eAIrE"}
1
+ {"version":3,"file":"styleContentSxy.d.ts","sourceRoot":"","sources":["../../src/style/styleContentSxy.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC9C,OAAO,EACH,KAAK,WAAW,EACnB,MAAM,mBAAmB,CAAA;AAa1B,wBAAgB,eAAe,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,eAIrE"}
@@ -4,15 +4,17 @@ import { warn } from "../output.js";
4
4
  import { TungstenError } from "../TungstenError.js";
5
5
  import { componentBumpClass } from "../componentClassName.js";
6
6
  import { $props, $type } from "../contentNodeSymbols.js";
7
+ import { isDev } from "../isDev.js";
8
+ import { getStyleUsage } from "./unusedStyles.js";
7
9
  function debug(...texts) {
8
10
  //console.log(...texts)
9
11
  }
10
12
  export function styleContentSxy(content, styles) {
11
13
  if (count(styles.tags) === 0 && count(styles.classes) === 0) return content; // optimization short circuit
12
- processElement(content, styles);
14
+ processElement(content, styles, isDev ? getStyleUsage(styles) : undefined);
13
15
  return content;
14
16
  }
15
- function processElement(element, styles) {
17
+ function processElement(element, styles, usage) {
16
18
  //console.log('element', element)
17
19
  debug('styles', styles);
18
20
  //debug(`---- style element ----`)
@@ -37,14 +39,15 @@ function processElement(element, styles) {
37
39
  if (Array.isArray(element)) {
38
40
  //debug('**** array element ****', element)
39
41
  for (const e of element) {
40
- processElement(e, styles);
42
+ processElement(e, styles, usage);
41
43
  }
42
44
  return;
43
45
  }
44
46
  debug('Element must be an element. Type: ', typeof element);
45
47
  // element is a ContentNodeElement
46
48
  let tagName;
47
- if (element[$props] !== undefined && element[$props].tagName !== undefined) {
49
+ //if (element[$props] !== undefined && element[$props].tagName !== undefined) {
50
+ if (element[$props].tagName !== undefined) {
48
51
  if (typeof element[$props].tagName !== 'string') {
49
52
  throw new TungstenError(`styleContentSxy() error: ${ContentNodeElementIsHtml(element) ? element[$type] : getComponentName(element)}` + ` component tagName field should be type string. Received ${typeof element[$props].tagName}`);
50
53
  }
@@ -97,6 +100,7 @@ function processElement(element, styles) {
97
100
  let newClassName = '';
98
101
  if (tagName in styles.tags) {
99
102
  newClassName += ' ' + componentBumpClass + ' ' + styles.tags[tagName];
103
+ usage?.usedTags.add(tagName);
100
104
  }
101
105
  //console.log(element.props.className)
102
106
  //if (element.props.className !== undefined) console.log(element.props.className)
@@ -110,6 +114,7 @@ function processElement(element, styles) {
110
114
  for (const cn of classNames) {
111
115
  if (cn in styles.classes) {
112
116
  newClassName += ` ${styles.classes[cn]}`;
117
+ usage?.usedClasses.add(cn);
113
118
  }
114
119
  }
115
120
  }
@@ -120,7 +125,7 @@ function processElement(element, styles) {
120
125
  if (element[$props].children !== undefined) {
121
126
  //debug(tagName + '^^^^children', element.props.children)
122
127
  //element.props.children = element.props.children as ContentNode
123
- processElement(element[$props].children, styles);
128
+ processElement(element[$props].children, styles, usage);
124
129
  }
125
130
  //console.log('element after mods', tagName, element)
126
131
  }
@@ -0,0 +1,11 @@
1
+ import type { CssNames } from '../CssNames.js';
2
+ export type StyleUsage = {
3
+ styles: CssNames;
4
+ usedTags: Set<string>;
5
+ usedClasses: Set<string>;
6
+ reportedTags: Set<string>;
7
+ reportedClasses: Set<string>;
8
+ };
9
+ export declare function getStyleUsage(styles: CssNames): StyleUsage;
10
+ export declare function reportUnusedStyles(): void;
11
+ //# sourceMappingURL=unusedStyles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unusedStyles.d.ts","sourceRoot":"","sources":["../../src/style/unusedStyles.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAU9C,MAAM,MAAM,UAAU,GAAG;IACrB,MAAM,EAAE,QAAQ,CAAA;IAChB,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACrB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACxB,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACzB,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAC/B,CAAA;AAUD,wBAAgB,aAAa,CAAC,MAAM,EAAE,QAAQ,cAc7C;AASD,wBAAgB,kBAAkB,SA0BjC"}
@@ -0,0 +1,43 @@
1
+ import { warn } from "../output.js";
2
+ // component stylesheets are module level singletons, so holding them here is not a leak
3
+ const usageByStyles = new Map();
4
+ // wait for render activity to settle before reporting, so that content rendered a little
5
+ // later (async components, route changes) doesn't get reported as unused
6
+ const reportDelayMs = 5000;
7
+ let reportHandle;
8
+ export function getStyleUsage(styles) {
9
+ let usage = usageByStyles.get(styles);
10
+ if (usage === undefined) {
11
+ usage = {
12
+ styles,
13
+ usedTags: new Set(),
14
+ usedClasses: new Set(),
15
+ reportedTags: new Set(),
16
+ reportedClasses: new Set()
17
+ };
18
+ usageByStyles.set(styles, usage);
19
+ }
20
+ scheduleUnusedStylesReport();
21
+ return usage;
22
+ }
23
+ function scheduleUnusedStylesReport() {
24
+ if (reportHandle !== undefined) clearTimeout(reportHandle);
25
+ reportHandle = setTimeout(reportUnusedStyles, reportDelayMs);
26
+ // don't hold a node process open just for the report
27
+ if (typeof reportHandle === 'object' && typeof reportHandle.unref === 'function') reportHandle.unref();
28
+ }
29
+ export function reportUnusedStyles() {
30
+ if (reportHandle !== undefined) {
31
+ clearTimeout(reportHandle);
32
+ reportHandle = undefined;
33
+ }
34
+ for (const usage of usageByStyles.values()) {
35
+ const tags = Object.keys(usage.styles.tags).filter(tag => !usage.usedTags.has(tag) && !usage.reportedTags.has(tag));
36
+ const classes = Object.keys(usage.styles.classes).filter(className => !usage.usedClasses.has(className) && !usage.reportedClasses.has(className));
37
+ if (tags.length === 0 && classes.length === 0) continue;
38
+ // only report each selector once, otherwise every later render repeats the warning
39
+ for (const tag of tags) usage.reportedTags.add(tag);
40
+ for (const className of classes) usage.reportedClasses.add(className);
41
+ warn(`Tungsten dev: styles in module "${usage.styles.moduleName}" have not matched any content:` + (tags.length > 0 ? `\n tag/component names: ${tags.join(', ')}` : '') + (classes.length > 0 ? `\n class names: ${classes.join(', ')}` : ''));
42
+ }
43
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tungsten",
3
- "version": "0.2.17",
3
+ "version": "0.2.18",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "bin": {