├── packages └── solid-optimizer │ ├── pridepack.json │ ├── test │ ├── compile.ts │ ├── __snapshots__ │ │ ├── onMount.test.ts.snap │ │ ├── createEffect.test.ts.snap │ │ ├── getListener.test.ts.snap │ │ ├── createDeferred.test.ts.snap │ │ ├── batch.test.ts.snap │ │ ├── untrack.test.ts.snap │ │ └── startTransition.test.ts.snap │ ├── getListener.test.ts │ ├── onMount.test.ts │ ├── createEffect.test.ts │ ├── createDeferred.test.ts │ ├── batch.test.ts │ ├── untrack.test.ts │ └── startTransition.test.ts │ ├── example.js │ ├── src │ ├── unwrap-path.ts │ ├── unwrap-node.ts │ ├── checks.ts │ ├── path-references-import.ts │ └── index.ts │ ├── tsconfig.json │ ├── README.md │ ├── package.json │ └── .gitignore ├── pnpm-workspace.yaml ├── package.json ├── lerna.json ├── .github └── workflows │ └── main.yml ├── LICENSE ├── README.md ├── .gitignore └── biome.json /packages/solid-optimizer/pridepack.json: -------------------------------------------------------------------------------- 1 | { 2 | "target": "es2018" 3 | } 4 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/**/* 3 | - examples/**/* 4 | onlyBuiltDependencies: 5 | - '@biomejs/biome' 6 | - esbuild 7 | - nx 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "workspaces": ["packages/*", "examples/*"], 5 | "devDependencies": { 6 | "@biomejs/biome": "^1.9.4", 7 | "lerna": "^8.2.1", 8 | "typescript": "^5.8.2" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/solid-optimizer/test/compile.ts: -------------------------------------------------------------------------------- 1 | import * as babel from '@babel/core'; 2 | import plugin from '../src'; 3 | 4 | export default async function compile(code: string) { 5 | const result = await babel.transformAsync(code, { 6 | plugins: [ 7 | [plugin], 8 | ], 9 | }); 10 | 11 | return result?.code; 12 | } 13 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmClient": "pnpm", 3 | "packages": [ 4 | "packages/*", 5 | "examples/*" 6 | ], 7 | "command": { 8 | "version": { 9 | "exact": true 10 | }, 11 | "publish": { 12 | "allowBranch": [ 13 | "main" 14 | ], 15 | "registry": "https://registry.npmjs.org/" 16 | } 17 | }, 18 | "version": "0.2.0" 19 | } -------------------------------------------------------------------------------- /packages/solid-optimizer/example.js: -------------------------------------------------------------------------------- 1 | import * as babel from '@babel/core'; 2 | import plugin from 'solid-optimizer'; 3 | 4 | async function compile(code) { 5 | const result = await babel.transformAsync(code, { 6 | plugins: [ 7 | [plugin], 8 | ], 9 | }); 10 | 11 | return result.code; 12 | } 13 | 14 | compile(` 15 | import { createEffect as x } from 'solid-js'; 16 | 17 | x(() => update()); 18 | `).then(console.log); -------------------------------------------------------------------------------- /packages/solid-optimizer/test/__snapshots__/onMount.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`onMount > should work for ImportSpecifier 1`] = `"import { onMount } from 'solid-js';"`; 4 | 5 | exports[`onMount > should work for aliased ImportSpecifier 1`] = `"import { onMount as x } from 'solid-js';"`; 6 | 7 | exports[`onMount > should work for aliased string ImportSpecifier 1`] = `"import { 'onMount' as x } from 'solid-js';"`; 8 | 9 | exports[`onMount > should work for namespace 1`] = `"import * as solid from 'solid-js';"`; 10 | -------------------------------------------------------------------------------- /packages/solid-optimizer/test/__snapshots__/createEffect.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`createEffect > should work for ImportSpecifier 1`] = `"import { createEffect } from 'solid-js';"`; 4 | 5 | exports[`createEffect > should work for aliased ImportSpecifier 1`] = `"import { createEffect as x } from 'solid-js';"`; 6 | 7 | exports[`createEffect > should work for aliased string ImportSpecifier 1`] = `"import { 'createEffect' as x } from 'solid-js';"`; 8 | 9 | exports[`createEffect > should work for namespace 1`] = `"import * as solid from 'solid-js';"`; 10 | -------------------------------------------------------------------------------- /packages/solid-optimizer/src/unwrap-path.ts: -------------------------------------------------------------------------------- 1 | import type * as babel from '@babel/core'; 2 | import type * as t from '@babel/types'; 3 | import { isNestedExpression, isPathValid } from './checks'; 4 | 5 | type TypeFilter = (node: t.Node) => node is V; 6 | 7 | export default function unwrapPath( 8 | path: unknown, 9 | key: TypeFilter, 10 | ): babel.NodePath | undefined { 11 | if (isPathValid(path, key)) { 12 | return path; 13 | } 14 | if (isPathValid(path, isNestedExpression)) { 15 | return unwrapPath(path.get('expression'), key); 16 | } 17 | return undefined; 18 | } 19 | -------------------------------------------------------------------------------- /packages/solid-optimizer/test/__snapshots__/getListener.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`getListener > should work for ImportSpecifier 1`] = ` 4 | "import { getListener } from 'solid-js'; 5 | null;" 6 | `; 7 | 8 | exports[`getListener > should work for aliased ImportSpecifier 1`] = ` 9 | "import { getListener as x } from 'solid-js'; 10 | null;" 11 | `; 12 | 13 | exports[`getListener > should work for aliased string ImportSpecifier 1`] = ` 14 | "import { 'getListener' as x } from 'solid-js'; 15 | null;" 16 | `; 17 | 18 | exports[`getListener > should work for namespace 1`] = ` 19 | "import * as solid from 'solid-js'; 20 | null;" 21 | `; 22 | -------------------------------------------------------------------------------- /packages/solid-optimizer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": [ 3 | "node_modules" 4 | ], 5 | "include": [ 6 | "src", 7 | "types" 8 | ], 9 | "compilerOptions": { 10 | "module": "ESNext", 11 | "lib": [ 12 | "ESNext", 13 | "DOM" 14 | ], 15 | "importHelpers": true, 16 | "declaration": true, 17 | "sourceMap": true, 18 | "rootDir": "./src", 19 | "strict": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "noImplicitReturns": true, 23 | "noFallthroughCasesInSwitch": true, 24 | "moduleResolution": "bundler", 25 | "jsx": "react", 26 | "esModuleInterop": true, 27 | "target": "ES2017", 28 | "useDefineForClassFields": false, 29 | "declarationMap": true 30 | } 31 | } -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | - push 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | 8 | steps: 9 | - uses: actions/checkout@v3 10 | - uses: pnpm/action-setup@v2.2.2 11 | with: 12 | version: 8 13 | run_install: | 14 | - recursive: true 15 | args: [--frozen-lockfile] 16 | 17 | - uses: actions/setup-node@v3 18 | with: 19 | node-version: 20 20 | cache: 'pnpm' 21 | 22 | - name: Clean 23 | run: pnpm recursive run clean 24 | env: 25 | CI: true 26 | 27 | - name: Build 28 | run: pnpm recursive run build 29 | env: 30 | CI: true 31 | 32 | - name: Begin Tests 33 | run: pnpm recursive run test 34 | env: 35 | CI: true 36 | -------------------------------------------------------------------------------- /packages/solid-optimizer/src/unwrap-node.ts: -------------------------------------------------------------------------------- 1 | import type * as t from '@babel/types'; 2 | 3 | type BroadTypeFilter = (node: t.Node) => node is K; 4 | type TypeCheck = K extends BroadTypeFilter ? U : never; 5 | 6 | type TypeFilter = (node: t.Node) => boolean; 7 | 8 | export default function unwrapNode( 9 | node: t.Node, 10 | key: K, 11 | ): TypeCheck | undefined { 12 | if (key(node)) { 13 | return node as TypeCheck; 14 | } 15 | switch (node.type) { 16 | case 'ParenthesizedExpression': 17 | case 'TypeCastExpression': 18 | case 'TSAsExpression': 19 | case 'TSSatisfiesExpression': 20 | case 'TSNonNullExpression': 21 | case 'TSTypeAssertion': 22 | case 'TSInstantiationExpression': 23 | return unwrapNode(node.expression, key); 24 | default: 25 | return undefined; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/solid-optimizer/test/getListener.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import compile from './compile'; 3 | 4 | describe('getListener', () => { 5 | it('should work for ImportSpecifier', async () => { 6 | expect(await compile(` 7 | import { getListener } from 'solid-js'; 8 | 9 | getListener(); 10 | `)).toMatchSnapshot(); 11 | }); 12 | it('should work for aliased ImportSpecifier', async () => { 13 | expect(await compile(` 14 | import { getListener as x } from 'solid-js'; 15 | 16 | x(); 17 | `)).toMatchSnapshot(); 18 | }); 19 | it('should work for aliased string ImportSpecifier', async () => { 20 | expect(await compile(` 21 | import { 'getListener' as x } from 'solid-js'; 22 | 23 | x(); 24 | `)).toMatchSnapshot(); 25 | }); 26 | it('should work for namespace', async () => { 27 | expect(await compile(` 28 | import * as solid from 'solid-js'; 29 | 30 | solid.getListener(); 31 | `)).toMatchSnapshot(); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Alexis Munsayac 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/solid-optimizer/test/onMount.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import compile from './compile'; 3 | 4 | describe('onMount', () => { 5 | it('should work for ImportSpecifier', async () => { 6 | expect(await compile(` 7 | import { onMount } from 'solid-js'; 8 | 9 | onMount(() => { 10 | console.log('Hello World'); 11 | }); 12 | `)).toMatchSnapshot(); 13 | }); 14 | it('should work for aliased ImportSpecifier', async () => { 15 | expect(await compile(` 16 | import { onMount as x } from 'solid-js'; 17 | 18 | x(() => { 19 | console.log('Hello World'); 20 | }); 21 | `)).toMatchSnapshot(); 22 | }); 23 | it('should work for aliased string ImportSpecifier', async () => { 24 | expect(await compile(` 25 | import { 'onMount' as x } from 'solid-js'; 26 | 27 | x(() => { 28 | console.log('Hello World'); 29 | }); 30 | `)).toMatchSnapshot(); 31 | }); 32 | it('should work for namespace', async () => { 33 | expect(await compile(` 34 | import * as solid from 'solid-js'; 35 | 36 | solid.onMount(() => { 37 | console.log('Hello World'); 38 | }); 39 | `)).toMatchSnapshot(); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /packages/solid-optimizer/test/createEffect.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import compile from './compile'; 3 | 4 | describe('createEffect', () => { 5 | it('should work for ImportSpecifier', async () => { 6 | expect(await compile(` 7 | import { createEffect } from 'solid-js'; 8 | 9 | createEffect(() => { 10 | console.log('Hello World'); 11 | }); 12 | `)).toMatchSnapshot(); 13 | }); 14 | it('should work for aliased ImportSpecifier', async () => { 15 | expect(await compile(` 16 | import { createEffect as x } from 'solid-js'; 17 | 18 | x(() => { 19 | console.log('Hello World'); 20 | }); 21 | `)).toMatchSnapshot(); 22 | }); 23 | it('should work for aliased string ImportSpecifier', async () => { 24 | expect(await compile(` 25 | import { 'createEffect' as x } from 'solid-js'; 26 | 27 | x(() => { 28 | console.log('Hello World'); 29 | }); 30 | `)).toMatchSnapshot(); 31 | }); 32 | it('should work for namespace', async () => { 33 | expect(await compile(` 34 | import * as solid from 'solid-js'; 35 | 36 | solid.createEffect(() => { 37 | console.log('Hello World'); 38 | }); 39 | `)).toMatchSnapshot(); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /packages/solid-optimizer/src/checks.ts: -------------------------------------------------------------------------------- 1 | import type * as babel from '@babel/core'; 2 | import type * as t from '@babel/types'; 3 | 4 | export function getImportSpecifierKey(specifier: t.ImportSpecifier): string { 5 | if (specifier.imported.type === 'Identifier') { 6 | return specifier.imported.name; 7 | } 8 | return specifier.imported.value; 9 | } 10 | 11 | type TypeFilter = (node: t.Node) => node is V; 12 | 13 | export function isPathValid( 14 | path: unknown, 15 | key: TypeFilter, 16 | ): path is babel.NodePath { 17 | return key((path as babel.NodePath).node); 18 | } 19 | 20 | export type NestedExpression = 21 | | t.ParenthesizedExpression 22 | | t.TypeCastExpression 23 | | t.TSAsExpression 24 | | t.TSSatisfiesExpression 25 | | t.TSNonNullExpression 26 | | t.TSInstantiationExpression 27 | | t.TSTypeAssertion; 28 | 29 | export function isNestedExpression(node: t.Node): node is NestedExpression { 30 | switch (node.type) { 31 | case 'ParenthesizedExpression': 32 | case 'TypeCastExpression': 33 | case 'TSAsExpression': 34 | case 'TSSatisfiesExpression': 35 | case 'TSNonNullExpression': 36 | case 'TSTypeAssertion': 37 | case 'TSInstantiationExpression': 38 | return true; 39 | default: 40 | return false; 41 | } 42 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # solid-optimizer 2 | 3 | > Experimental compile-time optimizer for SolidJS 4 | 5 | [![NPM](https://img.shields.io/npm/v/solid-optimizer.svg)](https://www.npmjs.com/package/solid-optimizer) [![JavaScript Style Guide](https://badgen.net/badge/code%20style/airbnb/ff5a5f?icon=airbnb)](https://github.com/airbnb/javascript) 6 | 7 | ## Install 8 | 9 | ```bash 10 | npm i -D solid-optimizer 11 | ``` 12 | 13 | ```bash 14 | yarn add -D solid-optimizer 15 | ``` 16 | 17 | ```bash 18 | pnpm add -D solid-optimizer 19 | ``` 20 | 21 | ## Features 22 | 23 | > **Warning** 24 | > The following features are only for SSR 25 | 26 | ### Trimming no-op 27 | 28 | The following are no-op functions in SSR, their calls are removed to enable tree-shaking unwanted code. 29 | 30 | - `createEffect` 31 | - `onMount` 32 | 33 | ### `untrack`, `batch` and `startTransition` 34 | 35 | Passed argument is inlined and called synchronously. For arrow functions, if the function doesn't have the body, it's return expression is inlined instead. 36 | 37 | ### `createDeferred` 38 | 39 | Passed argument is inlined. 40 | 41 | ### `getListener` 42 | 43 | `getListener` calls are replaced with `null`. 44 | 45 | ## Sponsors 46 | 47 | ![Sponsors](https://github.com/lxsmnsyc/sponsors/blob/main/sponsors.svg?raw=true) 48 | 49 | ## License 50 | 51 | MIT © [lxsmnsyc](https://github.com/lxsmnsyc) 52 | -------------------------------------------------------------------------------- /packages/solid-optimizer/README.md: -------------------------------------------------------------------------------- 1 | # solid-optimizer 2 | 3 | > Experimental compile-time optimizer for SolidJS 4 | 5 | [![NPM](https://img.shields.io/npm/v/solid-optimizer.svg)](https://www.npmjs.com/package/solid-optimizer) [![JavaScript Style Guide](https://badgen.net/badge/code%20style/airbnb/ff5a5f?icon=airbnb)](https://github.com/airbnb/javascript) 6 | 7 | ## Install 8 | 9 | ```bash 10 | npm i -D solid-optimizer 11 | ``` 12 | 13 | ```bash 14 | yarn add -D solid-optimizer 15 | ``` 16 | 17 | ```bash 18 | pnpm add -D solid-optimizer 19 | ``` 20 | 21 | ## Features 22 | 23 | > **Warning** 24 | > The following features are only for SSR 25 | 26 | ### Trimming no-op 27 | 28 | The following are no-op functions in SSR, their calls are removed to enable tree-shaking unwanted code. 29 | 30 | - `createEffect` 31 | - `onMount` 32 | 33 | ### `untrack`, `batch` and `startTransition` 34 | 35 | Passed argument is inlined and called synchronously. For arrow functions, if the function doesn't have the body, it's return expression is inlined instead. 36 | 37 | ### `createDeferred` 38 | 39 | Passed argument is inlined. 40 | 41 | ### `getListener` 42 | 43 | `getListener` calls are replaced with `null`. 44 | 45 | ## Sponsors 46 | 47 | ![Sponsors](https://github.com/lxsmnsyc/sponsors/blob/main/sponsors.svg?raw=true) 48 | ## License 49 | 50 | MIT © [lxsmnsyc](https://github.com/lxsmnsyc) 51 | -------------------------------------------------------------------------------- /packages/solid-optimizer/test/__snapshots__/createDeferred.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`createDeferred > Expression arguments > should work for ImportSpecifier 1`] = ` 4 | "import { createDeferred } from 'solid-js'; 5 | () => update();" 6 | `; 7 | 8 | exports[`createDeferred > Expression arguments > should work for aliased ImportSpecifier 1`] = ` 9 | "import { createDeferred as x } from 'solid-js'; 10 | () => update();" 11 | `; 12 | 13 | exports[`createDeferred > Expression arguments > should work for aliased string ImportSpecifier 1`] = ` 14 | "import { 'createDeferred' as x } from 'solid-js'; 15 | () => update();" 16 | `; 17 | 18 | exports[`createDeferred > Expression arguments > should work for namespace 1`] = ` 19 | "import * as solid from 'solid-js'; 20 | () => update();" 21 | `; 22 | 23 | exports[`createDeferred > spreads > should work for ImportSpecifier 1`] = ` 24 | "import { createDeferred } from 'solid-js'; 25 | example[0];" 26 | `; 27 | 28 | exports[`createDeferred > spreads > should work for aliased ImportSpecifier 1`] = ` 29 | "import { createDeferred as x } from 'solid-js'; 30 | example[0];" 31 | `; 32 | 33 | exports[`createDeferred > spreads > should work for aliased string ImportSpecifier 1`] = ` 34 | "import { 'createDeferred' as x } from 'solid-js'; 35 | example[0];" 36 | `; 37 | 38 | exports[`createDeferred > spreads > should work for namespace 1`] = ` 39 | "import * as solid from 'solid-js'; 40 | example[0];" 41 | `; 42 | -------------------------------------------------------------------------------- /packages/solid-optimizer/test/__snapshots__/batch.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`batch > callbacks w/o body > should work for ImportSpecifier 1`] = ` 4 | "import { batch } from 'solid-js'; 5 | update();" 6 | `; 7 | 8 | exports[`batch > callbacks w/o body > should work for aliased ImportSpecifier 1`] = ` 9 | "import { batch as x } from 'solid-js'; 10 | update();" 11 | `; 12 | 13 | exports[`batch > callbacks w/o body > should work for aliased string ImportSpecifier 1`] = ` 14 | "import { 'batch' as x } from 'solid-js'; 15 | update();" 16 | `; 17 | 18 | exports[`batch > callbacks w/o body > should work for namespace 1`] = ` 19 | "import * as solid from 'solid-js'; 20 | update();" 21 | `; 22 | 23 | exports[`batch > callbacks with body > should work for ImportSpecifier 1`] = ` 24 | "import { batch } from 'solid-js'; 25 | (() => { 26 | update(); 27 | })();" 28 | `; 29 | 30 | exports[`batch > callbacks with body > should work for aliased ImportSpecifier 1`] = ` 31 | "import { batch as x } from 'solid-js'; 32 | (() => { 33 | update(); 34 | })();" 35 | `; 36 | 37 | exports[`batch > callbacks with body > should work for aliased string ImportSpecifier 1`] = ` 38 | "import { 'batch' as x } from 'solid-js'; 39 | (() => { 40 | update(); 41 | })();" 42 | `; 43 | 44 | exports[`batch > callbacks with body > should work for namespace 1`] = ` 45 | "import * as solid from 'solid-js'; 46 | (() => { 47 | update(); 48 | })();" 49 | `; 50 | 51 | exports[`batch > spreads > should work for ImportSpecifier 1`] = ` 52 | "import { batch } from 'solid-js'; 53 | example[0]();" 54 | `; 55 | 56 | exports[`batch > spreads > should work for aliased ImportSpecifier 1`] = ` 57 | "import { batch as x } from 'solid-js'; 58 | example[0]();" 59 | `; 60 | 61 | exports[`batch > spreads > should work for aliased string ImportSpecifier 1`] = ` 62 | "import { 'batch' as x } from 'solid-js'; 63 | example[0]();" 64 | `; 65 | 66 | exports[`batch > spreads > should work for namespace 1`] = ` 67 | "import * as solid from 'solid-js'; 68 | example[0]();" 69 | `; 70 | -------------------------------------------------------------------------------- /packages/solid-optimizer/test/__snapshots__/untrack.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`untrack > callbacks w/o body > should work for ImportSpecifier 1`] = ` 4 | "import { untrack } from 'solid-js'; 5 | update();" 6 | `; 7 | 8 | exports[`untrack > callbacks w/o body > should work for aliased ImportSpecifier 1`] = ` 9 | "import { untrack as x } from 'solid-js'; 10 | update();" 11 | `; 12 | 13 | exports[`untrack > callbacks w/o body > should work for aliased string ImportSpecifier 1`] = ` 14 | "import { 'untrack' as x } from 'solid-js'; 15 | update();" 16 | `; 17 | 18 | exports[`untrack > callbacks w/o body > should work for namespace 1`] = ` 19 | "import * as solid from 'solid-js'; 20 | update();" 21 | `; 22 | 23 | exports[`untrack > callbacks with body > should work for ImportSpecifier 1`] = ` 24 | "import { untrack } from 'solid-js'; 25 | (() => { 26 | update(); 27 | })();" 28 | `; 29 | 30 | exports[`untrack > callbacks with body > should work for aliased ImportSpecifier 1`] = ` 31 | "import { untrack as x } from 'solid-js'; 32 | (() => { 33 | update(); 34 | })();" 35 | `; 36 | 37 | exports[`untrack > callbacks with body > should work for aliased string ImportSpecifier 1`] = ` 38 | "import { 'untrack' as x } from 'solid-js'; 39 | (() => { 40 | update(); 41 | })();" 42 | `; 43 | 44 | exports[`untrack > callbacks with body > should work for namespace 1`] = ` 45 | "import * as solid from 'solid-js'; 46 | (() => { 47 | update(); 48 | })();" 49 | `; 50 | 51 | exports[`untrack > spreads > should work for ImportSpecifier 1`] = ` 52 | "import { untrack } from 'solid-js'; 53 | example[0]();" 54 | `; 55 | 56 | exports[`untrack > spreads > should work for aliased ImportSpecifier 1`] = ` 57 | "import { untrack as x } from 'solid-js'; 58 | example[0]();" 59 | `; 60 | 61 | exports[`untrack > spreads > should work for aliased string ImportSpecifier 1`] = ` 62 | "import { 'untrack' as x } from 'solid-js'; 63 | example[0]();" 64 | `; 65 | 66 | exports[`untrack > spreads > should work for namespace 1`] = ` 67 | "import * as solid from 'solid-js'; 68 | example[0]();" 69 | `; 70 | -------------------------------------------------------------------------------- /packages/solid-optimizer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "solid-optimizer", 3 | "version": "0.2.0", 4 | "type": "module", 5 | "files": [ 6 | "dist", 7 | "src" 8 | ], 9 | "engines": { 10 | "node": ">=10" 11 | }, 12 | "license": "MIT", 13 | "keywords": [ 14 | "pridepack" 15 | ], 16 | "dependencies": { 17 | "@babel/traverse": "^7.26.10", 18 | "@babel/types": "^7.26.10" 19 | }, 20 | "devDependencies": { 21 | "@babel/core": "^7.26.10", 22 | "@types/babel__core": "^7.20.5", 23 | "@types/babel__traverse": "^7.20.6", 24 | "@types/node": "^22.13.10", 25 | "pridepack": "2.6.4", 26 | "tslib": "^2.8.1", 27 | "typescript": "^5.8.2", 28 | "vitest": "^3.0.8" 29 | }, 30 | "peerDependencies": { 31 | "@babel/core": "^7.26" 32 | }, 33 | "scripts": { 34 | "prepublishOnly": "pridepack clean && pridepack build", 35 | "build": "pridepack build", 36 | "type-check": "pridepack check", 37 | "clean": "pridepack clean", 38 | "watch": "pridepack watch", 39 | "start": "pridepack start", 40 | "dev": "pridepack dev", 41 | "test": "vitest" 42 | }, 43 | "description": "Experimental compile-time optimizer for SolidJS", 44 | "repository": { 45 | "url": "https://github.com/lxsmnsyc/solid-optimizer.git", 46 | "type": "git" 47 | }, 48 | "homepage": "https://github.com/lxsmnsyc/solid-optimizer/packages/solid-optimizer", 49 | "bugs": { 50 | "url": "https://github.com/lxsmnsyc/solid-optimizer/issues" 51 | }, 52 | "publishConfig": { 53 | "access": "public" 54 | }, 55 | "author": "Alexis Munsayac", 56 | "private": false, 57 | "types": "./dist/types/index.d.ts", 58 | "main": "./dist/cjs/production/index.cjs", 59 | "module": "./dist/esm/production/index.mjs", 60 | "exports": { 61 | ".": { 62 | "types": "./dist/types/index.d.ts", 63 | "development": { 64 | "require": "./dist/cjs/development/index.cjs", 65 | "import": "./dist/esm/development/index.mjs" 66 | }, 67 | "require": "./dist/cjs/production/index.cjs", 68 | "import": "./dist/esm/production/index.mjs" 69 | } 70 | }, 71 | "typesVersions": { 72 | "*": {} 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/solid-optimizer/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.production 74 | .env.development 75 | 76 | # parcel-bundler cache (https://parceljs.org/) 77 | .cache 78 | 79 | # Next.js build output 80 | .next 81 | 82 | # Nuxt.js build / generate output 83 | .nuxt 84 | dist 85 | 86 | # Gatsby files 87 | .cache/ 88 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 89 | # https://nextjs.org/blog/next-9-1#public-directory-support 90 | # public 91 | 92 | # vuepress build output 93 | .vuepress/dist 94 | 95 | # Serverless directories 96 | .serverless/ 97 | 98 | # FuseBox cache 99 | .fusebox/ 100 | 101 | # DynamoDB Local files 102 | .dynamodb/ 103 | 104 | # TernJS port file 105 | .tern-port 106 | 107 | .npmrc 108 | -------------------------------------------------------------------------------- /packages/solid-optimizer/test/createDeferred.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import compile from './compile'; 3 | 4 | describe('createDeferred', () => { 5 | describe('Expression arguments', () => { 6 | it('should work for ImportSpecifier', async () => { 7 | expect(await compile(` 8 | import { createDeferred } from 'solid-js'; 9 | 10 | createDeferred(() => update()); 11 | `)).toMatchSnapshot(); 12 | }); 13 | it('should work for aliased ImportSpecifier', async () => { 14 | expect(await compile(` 15 | import { createDeferred as x } from 'solid-js'; 16 | 17 | x(() => update()); 18 | `)).toMatchSnapshot(); 19 | }); 20 | it('should work for aliased string ImportSpecifier', async () => { 21 | expect(await compile(` 22 | import { 'createDeferred' as x } from 'solid-js'; 23 | 24 | x(() => update()); 25 | `)).toMatchSnapshot(); 26 | }); 27 | it('should work for namespace', async () => { 28 | expect(await compile(` 29 | import * as solid from 'solid-js'; 30 | 31 | solid.createDeferred(() => update()); 32 | `)).toMatchSnapshot(); 33 | }); 34 | }); 35 | describe('spreads', () => { 36 | it('should work for ImportSpecifier', async () => { 37 | expect(await compile(` 38 | import { createDeferred } from 'solid-js'; 39 | 40 | createDeferred(...example); 41 | `)).toMatchSnapshot(); 42 | }); 43 | it('should work for aliased ImportSpecifier', async () => { 44 | expect(await compile(` 45 | import { createDeferred as x } from 'solid-js'; 46 | 47 | x(...example); 48 | `)).toMatchSnapshot(); 49 | }); 50 | it('should work for aliased string ImportSpecifier', async () => { 51 | expect(await compile(` 52 | import { 'createDeferred' as x } from 'solid-js'; 53 | 54 | x(...example); 55 | `)).toMatchSnapshot(); 56 | }); 57 | it('should work for namespace', async () => { 58 | expect(await compile(` 59 | import * as solid from 'solid-js'; 60 | 61 | solid.createDeferred(...example); 62 | `)).toMatchSnapshot(); 63 | }); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.production 74 | .env.development 75 | 76 | # parcel-bundler cache (https://parceljs.org/) 77 | .cache 78 | 79 | # Next.js build output 80 | .next 81 | 82 | # Nuxt.js build / generate output 83 | .nuxt 84 | dist 85 | 86 | # Gatsby files 87 | .cache/ 88 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 89 | # https://nextjs.org/blog/next-9-1#public-directory-support 90 | # public 91 | 92 | # vuepress build output 93 | .vuepress/dist 94 | 95 | # Serverless directories 96 | .serverless/ 97 | 98 | # FuseBox cache 99 | .fusebox/ 100 | 101 | # DynamoDB Local files 102 | .dynamodb/ 103 | 104 | # TernJS port file 105 | .tern-port 106 | 107 | .npmrc 108 | .poneglyph 109 | 110 | .rigidity 111 | 112 | .vercel 113 | -------------------------------------------------------------------------------- /packages/solid-optimizer/test/__snapshots__/startTransition.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`startTransition > callbacks w/o body > should work for ImportSpecifier 1`] = ` 4 | "import { startTransition } from 'solid-js'; 5 | update();" 6 | `; 7 | 8 | exports[`startTransition > callbacks w/o body > should work for aliased ImportSpecifier 1`] = ` 9 | "import { startTransition as x } from 'solid-js'; 10 | update();" 11 | `; 12 | 13 | exports[`startTransition > callbacks w/o body > should work for aliased string ImportSpecifier 1`] = ` 14 | "import { 'startTransition' as x } from 'solid-js'; 15 | update();" 16 | `; 17 | 18 | exports[`startTransition > callbacks w/o body > should work for namespace 1`] = ` 19 | "import * as solid from 'solid-js'; 20 | update();" 21 | `; 22 | 23 | exports[`startTransition > callbacks with body > should work for ImportSpecifier 1`] = ` 24 | "import { startTransition } from 'solid-js'; 25 | (() => { 26 | update(); 27 | })();" 28 | `; 29 | 30 | exports[`startTransition > callbacks with body > should work for aliased ImportSpecifier 1`] = ` 31 | "import { startTransition as x } from 'solid-js'; 32 | (() => { 33 | update(); 34 | })();" 35 | `; 36 | 37 | exports[`startTransition > callbacks with body > should work for aliased string ImportSpecifier 1`] = ` 38 | "import { 'startTransition' as x } from 'solid-js'; 39 | (() => { 40 | update(); 41 | })();" 42 | `; 43 | 44 | exports[`startTransition > callbacks with body > should work for namespace 1`] = ` 45 | "import * as solid from 'solid-js'; 46 | (() => { 47 | update(); 48 | })();" 49 | `; 50 | 51 | exports[`startTransition > spreads > should work for ImportSpecifier 1`] = ` 52 | "import { startTransition } from 'solid-js'; 53 | example[0]();" 54 | `; 55 | 56 | exports[`startTransition > spreads > should work for aliased ImportSpecifier 1`] = ` 57 | "import { startTransition as x } from 'solid-js'; 58 | example[0]();" 59 | `; 60 | 61 | exports[`startTransition > spreads > should work for aliased string ImportSpecifier 1`] = ` 62 | "import { 'startTransition' as x } from 'solid-js'; 63 | example[0]();" 64 | `; 65 | 66 | exports[`startTransition > spreads > should work for namespace 1`] = ` 67 | "import * as solid from 'solid-js'; 68 | example[0]();" 69 | `; 70 | -------------------------------------------------------------------------------- /packages/solid-optimizer/src/path-references-import.ts: -------------------------------------------------------------------------------- 1 | import type { NodePath } from '@babel/core'; 2 | import * as t from '@babel/types'; 3 | import { isPathValid } from './checks'; 4 | import unwrapPath from './unwrap-path'; 5 | 6 | function pathReferencesImportIdentifier( 7 | path: NodePath, 8 | moduleSources: string[], 9 | importNames: string[], 10 | ): boolean { 11 | const identifier = unwrapPath(path, t.isIdentifier); 12 | if (!identifier) { 13 | return false; 14 | } 15 | const binding = path.scope.getBinding(identifier.node.name); 16 | if (!binding || binding.kind !== 'module') { 17 | return false; 18 | } 19 | const importPath = binding.path; 20 | const importParent = importPath.parentPath; 21 | if ( 22 | isPathValid(importParent, t.isImportDeclaration) && 23 | moduleSources.includes(importParent.node.source.value) 24 | ) { 25 | if (isPathValid(importPath, t.isImportSpecifier)) { 26 | const key = t.isIdentifier(importPath.node.imported) 27 | ? importPath.node.imported.name 28 | : importPath.node.imported.value; 29 | return importNames.includes(key); 30 | } 31 | if (isPathValid(importPath, t.isImportDefaultSpecifier)) { 32 | return importNames.includes('default'); 33 | } 34 | if (isPathValid(importPath, t.isImportNamespaceSpecifier)) { 35 | return importNames.includes('*'); 36 | } 37 | } 38 | return false; 39 | } 40 | 41 | const IMPORT_NAMESPACE = ['*']; 42 | 43 | function pathReferencesImportMember( 44 | path: NodePath, 45 | moduleSources: string[], 46 | importNames: string[], 47 | ): boolean { 48 | const memberExpr = 49 | unwrapPath(path, t.isMemberExpression) || 50 | unwrapPath(path, t.isOptionalMemberExpression); 51 | if (memberExpr) { 52 | const object = unwrapPath(memberExpr.get('object'), t.isIdentifier); 53 | if (!object) { 54 | return false; 55 | } 56 | const property = memberExpr.get('property'); 57 | if (isPathValid(property, t.isIdentifier)) { 58 | return ( 59 | importNames.includes(property.node.name) && 60 | pathReferencesImport(object, moduleSources, IMPORT_NAMESPACE) 61 | ); 62 | } 63 | if (isPathValid(property, t.isStringLiteral)) { 64 | return ( 65 | importNames.includes(property.node.value) && 66 | pathReferencesImport(object, moduleSources, IMPORT_NAMESPACE) 67 | ); 68 | } 69 | } 70 | return false; 71 | } 72 | 73 | export function pathReferencesImport( 74 | path: NodePath, 75 | moduleSources: string[], 76 | importNames: string[], 77 | ): boolean { 78 | return ( 79 | pathReferencesImportIdentifier(path, moduleSources, importNames) || 80 | pathReferencesImportMember(path, moduleSources, importNames) 81 | ); 82 | } 83 | -------------------------------------------------------------------------------- /packages/solid-optimizer/test/batch.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import compile from './compile'; 3 | 4 | describe('batch', () => { 5 | describe('callbacks w/o body', () => { 6 | it('should work for ImportSpecifier', async () => { 7 | expect(await compile(` 8 | import { batch } from 'solid-js'; 9 | 10 | batch(() => update()); 11 | `)).toMatchSnapshot(); 12 | }); 13 | it('should work for aliased ImportSpecifier', async () => { 14 | expect(await compile(` 15 | import { batch as x } from 'solid-js'; 16 | 17 | x(() => update()); 18 | `)).toMatchSnapshot(); 19 | }); 20 | it('should work for aliased string ImportSpecifier', async () => { 21 | expect(await compile(` 22 | import { 'batch' as x } from 'solid-js'; 23 | 24 | x(() => update()); 25 | `)).toMatchSnapshot(); 26 | }); 27 | it('should work for namespace', async () => { 28 | expect(await compile(` 29 | import * as solid from 'solid-js'; 30 | 31 | solid.batch(() => update()); 32 | `)).toMatchSnapshot(); 33 | }); 34 | }); 35 | describe('callbacks with body', () => { 36 | it('should work for ImportSpecifier', async () => { 37 | expect(await compile(` 38 | import { batch } from 'solid-js'; 39 | 40 | batch(() => { 41 | update(); 42 | }); 43 | `)).toMatchSnapshot(); 44 | }); 45 | it('should work for aliased ImportSpecifier', async () => { 46 | expect(await compile(` 47 | import { batch as x } from 'solid-js'; 48 | 49 | x(() => { 50 | update(); 51 | }); 52 | `)).toMatchSnapshot(); 53 | }); 54 | it('should work for aliased string ImportSpecifier', async () => { 55 | expect(await compile(` 56 | import { 'batch' as x } from 'solid-js'; 57 | 58 | x(() => { 59 | update(); 60 | }); 61 | `)).toMatchSnapshot(); 62 | }); 63 | it('should work for namespace', async () => { 64 | expect(await compile(` 65 | import * as solid from 'solid-js'; 66 | 67 | solid.batch(() => { 68 | update(); 69 | }); 70 | `)).toMatchSnapshot(); 71 | }); 72 | }); 73 | describe('spreads', () => { 74 | it('should work for ImportSpecifier', async () => { 75 | expect(await compile(` 76 | import { batch } from 'solid-js'; 77 | 78 | batch(...example); 79 | `)).toMatchSnapshot(); 80 | }); 81 | it('should work for aliased ImportSpecifier', async () => { 82 | expect(await compile(` 83 | import { batch as x } from 'solid-js'; 84 | 85 | x(...example); 86 | `)).toMatchSnapshot(); 87 | }); 88 | it('should work for aliased string ImportSpecifier', async () => { 89 | expect(await compile(` 90 | import { 'batch' as x } from 'solid-js'; 91 | 92 | x(...example); 93 | `)).toMatchSnapshot(); 94 | }); 95 | it('should work for namespace', async () => { 96 | expect(await compile(` 97 | import * as solid from 'solid-js'; 98 | 99 | solid.batch(...example); 100 | `)).toMatchSnapshot(); 101 | }); 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /packages/solid-optimizer/test/untrack.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import compile from './compile'; 3 | 4 | describe('untrack', () => { 5 | describe('callbacks w/o body', () => { 6 | it('should work for ImportSpecifier', async () => { 7 | expect(await compile(` 8 | import { untrack } from 'solid-js'; 9 | 10 | untrack(() => update()); 11 | `)).toMatchSnapshot(); 12 | }); 13 | it('should work for aliased ImportSpecifier', async () => { 14 | expect(await compile(` 15 | import { untrack as x } from 'solid-js'; 16 | 17 | x(() => update()); 18 | `)).toMatchSnapshot(); 19 | }); 20 | it('should work for aliased string ImportSpecifier', async () => { 21 | expect(await compile(` 22 | import { 'untrack' as x } from 'solid-js'; 23 | 24 | x(() => update()); 25 | `)).toMatchSnapshot(); 26 | }); 27 | it('should work for namespace', async () => { 28 | expect(await compile(` 29 | import * as solid from 'solid-js'; 30 | 31 | solid.untrack(() => update()); 32 | `)).toMatchSnapshot(); 33 | }); 34 | }); 35 | describe('callbacks with body', () => { 36 | it('should work for ImportSpecifier', async () => { 37 | expect(await compile(` 38 | import { untrack } from 'solid-js'; 39 | 40 | untrack(() => { 41 | update(); 42 | }); 43 | `)).toMatchSnapshot(); 44 | }); 45 | it('should work for aliased ImportSpecifier', async () => { 46 | expect(await compile(` 47 | import { untrack as x } from 'solid-js'; 48 | 49 | x(() => { 50 | update(); 51 | }); 52 | `)).toMatchSnapshot(); 53 | }); 54 | it('should work for aliased string ImportSpecifier', async () => { 55 | expect(await compile(` 56 | import { 'untrack' as x } from 'solid-js'; 57 | 58 | x(() => { 59 | update(); 60 | }); 61 | `)).toMatchSnapshot(); 62 | }); 63 | it('should work for namespace', async () => { 64 | expect(await compile(` 65 | import * as solid from 'solid-js'; 66 | 67 | solid.untrack(() => { 68 | update(); 69 | }); 70 | `)).toMatchSnapshot(); 71 | }); 72 | }); 73 | describe('spreads', () => { 74 | it('should work for ImportSpecifier', async () => { 75 | expect(await compile(` 76 | import { untrack } from 'solid-js'; 77 | 78 | untrack(...example); 79 | `)).toMatchSnapshot(); 80 | }); 81 | it('should work for aliased ImportSpecifier', async () => { 82 | expect(await compile(` 83 | import { untrack as x } from 'solid-js'; 84 | 85 | x(...example); 86 | `)).toMatchSnapshot(); 87 | }); 88 | it('should work for aliased string ImportSpecifier', async () => { 89 | expect(await compile(` 90 | import { 'untrack' as x } from 'solid-js'; 91 | 92 | x(...example); 93 | `)).toMatchSnapshot(); 94 | }); 95 | it('should work for namespace', async () => { 96 | expect(await compile(` 97 | import * as solid from 'solid-js'; 98 | 99 | solid.untrack(...example); 100 | `)).toMatchSnapshot(); 101 | }); 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /packages/solid-optimizer/test/startTransition.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import compile from './compile'; 3 | 4 | describe('startTransition', () => { 5 | describe('callbacks w/o body', () => { 6 | it('should work for ImportSpecifier', async () => { 7 | expect(await compile(` 8 | import { startTransition } from 'solid-js'; 9 | 10 | startTransition(() => update()); 11 | `)).toMatchSnapshot(); 12 | }); 13 | it('should work for aliased ImportSpecifier', async () => { 14 | expect(await compile(` 15 | import { startTransition as x } from 'solid-js'; 16 | 17 | x(() => update()); 18 | `)).toMatchSnapshot(); 19 | }); 20 | it('should work for aliased string ImportSpecifier', async () => { 21 | expect(await compile(` 22 | import { 'startTransition' as x } from 'solid-js'; 23 | 24 | x(() => update()); 25 | `)).toMatchSnapshot(); 26 | }); 27 | it('should work for namespace', async () => { 28 | expect(await compile(` 29 | import * as solid from 'solid-js'; 30 | 31 | solid.startTransition(() => update()); 32 | `)).toMatchSnapshot(); 33 | }); 34 | }); 35 | describe('callbacks with body', () => { 36 | it('should work for ImportSpecifier', async () => { 37 | expect(await compile(` 38 | import { startTransition } from 'solid-js'; 39 | 40 | startTransition(() => { 41 | update(); 42 | }); 43 | `)).toMatchSnapshot(); 44 | }); 45 | it('should work for aliased ImportSpecifier', async () => { 46 | expect(await compile(` 47 | import { startTransition as x } from 'solid-js'; 48 | 49 | x(() => { 50 | update(); 51 | }); 52 | `)).toMatchSnapshot(); 53 | }); 54 | it('should work for aliased string ImportSpecifier', async () => { 55 | expect(await compile(` 56 | import { 'startTransition' as x } from 'solid-js'; 57 | 58 | x(() => { 59 | update(); 60 | }); 61 | `)).toMatchSnapshot(); 62 | }); 63 | it('should work for namespace', async () => { 64 | expect(await compile(` 65 | import * as solid from 'solid-js'; 66 | 67 | solid.startTransition(() => { 68 | update(); 69 | }); 70 | `)).toMatchSnapshot(); 71 | }); 72 | }); 73 | describe('spreads', () => { 74 | it('should work for ImportSpecifier', async () => { 75 | expect(await compile(` 76 | import { startTransition } from 'solid-js'; 77 | 78 | startTransition(...example); 79 | `)).toMatchSnapshot(); 80 | }); 81 | it('should work for aliased ImportSpecifier', async () => { 82 | expect(await compile(` 83 | import { startTransition as x } from 'solid-js'; 84 | 85 | x(...example); 86 | `)).toMatchSnapshot(); 87 | }); 88 | it('should work for aliased string ImportSpecifier', async () => { 89 | expect(await compile(` 90 | import { 'startTransition' as x } from 'solid-js'; 91 | 92 | x(...example); 93 | `)).toMatchSnapshot(); 94 | }); 95 | it('should work for namespace', async () => { 96 | expect(await compile(` 97 | import * as solid from 'solid-js'; 98 | 99 | solid.startTransition(...example); 100 | `)).toMatchSnapshot(); 101 | }); 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /packages/solid-optimizer/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { NodePath, PluginObj } from '@babel/core'; 2 | import * as t from '@babel/types'; 3 | import { pathReferencesImport } from './path-references-import'; 4 | import unwrapNode from './unwrap-node'; 5 | 6 | function transformUntrackAndBatch(path: NodePath): void { 7 | // https://github.com/solidjs/solid/blob/41fa6c14b4bf71eed593303cd63b32d53e3515e9/packages/solid/src/server/reactive.ts#L130-L134 8 | const arg = path.node.arguments[0]; 9 | if (t.isExpression(arg)) { 10 | const trueArrow = unwrapNode(arg, t.isArrowFunctionExpression); 11 | if (trueArrow) { 12 | if (t.isExpression(trueArrow.body)) { 13 | path.replaceWith(trueArrow.body); 14 | } else { 15 | path.replaceWith(t.callExpression(trueArrow, [])); 16 | } 17 | return; 18 | } 19 | const trueFunc = unwrapNode(arg, t.isFunctionExpression); 20 | if (trueFunc) { 21 | path.replaceWith(trueFunc.body); 22 | } 23 | } else if (t.isSpreadElement(arg)) { 24 | path.replaceWith( 25 | t.callExpression( 26 | t.memberExpression(arg.argument, t.numericLiteral(0), true), 27 | [], 28 | ), 29 | ); 30 | } 31 | } 32 | 33 | function transformStartTransition(path: NodePath): void { 34 | // https://github.com/solidjs/solid/blob/main/packages/solid/src/server/rendering.ts#L488 35 | const arg = path.node.arguments[0]; 36 | if (t.isExpression(arg)) { 37 | const trueArrow = unwrapNode(arg, t.isArrowFunctionExpression); 38 | if (trueArrow) { 39 | if (t.isExpression(trueArrow.body)) { 40 | path.replaceWith(trueArrow.body); 41 | } else { 42 | path.replaceWith(t.callExpression(trueArrow, [])); 43 | } 44 | return; 45 | } 46 | const trueFunc = unwrapNode(arg, t.isFunctionExpression); 47 | if (trueFunc) { 48 | path.replaceWith(trueFunc.body); 49 | } 50 | } else if (t.isSpreadElement(arg)) { 51 | path.replaceWith( 52 | t.callExpression( 53 | t.memberExpression(arg.argument, t.numericLiteral(0), true), 54 | [], 55 | ), 56 | ); 57 | } 58 | } 59 | 60 | function transformCreateDeferred(path: NodePath): void { 61 | // https://github.com/solidjs/solid/blob/main/packages/solid/src/server/reactive.ts#L99 62 | const arg = path.node.arguments[0]; 63 | if (t.isExpression(arg)) { 64 | path.replaceWith(arg); 65 | } else if (t.isSpreadElement(arg)) { 66 | path.replaceWith( 67 | t.memberExpression(arg.argument, t.numericLiteral(0), true), 68 | ); 69 | } 70 | } 71 | 72 | const MODULES = ['solid-js', 'solid-js/web']; 73 | 74 | function transformCallExpression(path: NodePath): void { 75 | const callee = path.get('callee'); 76 | 77 | if (pathReferencesImport(callee, MODULES, ['createEffect', 'onMount'])) { 78 | // https://github.com/solidjs/solid/blob/41fa6c14b4bf71eed593303cd63b32d53e3515e9/packages/solid/src/server/reactive.ts#L101 79 | // https://github.com/solidjs/solid/blob/41fa6c14b4bf71eed593303cd63b32d53e3515e9/packages/solid/src/server/reactive.ts#L154 80 | // Just remove it 81 | path.remove(); 82 | } else if (pathReferencesImport(callee, MODULES, ['getListener'])) { 83 | // https://github.com/solidjs/solid/blob/41fa6c14b4bf71eed593303cd63b32d53e3515e9/packages/solid/src/server/reactive.ts#L188 84 | path.replaceWith(t.nullLiteral()); 85 | } else if (pathReferencesImport(callee, MODULES, ['untrack', 'batch'])) { 86 | transformUntrackAndBatch(path); 87 | } else if (pathReferencesImport(callee, MODULES, ['startTransition'])) { 88 | transformStartTransition(path); 89 | } else if (pathReferencesImport(callee, MODULES, ['createDeferred'])) { 90 | transformCreateDeferred(path); 91 | } 92 | } 93 | 94 | export default function solidOptimizerPlugin(): PluginObj { 95 | return { 96 | name: 'solid-optimizer', 97 | visitor: { 98 | Program(program) { 99 | program.traverse({ 100 | CallExpression(path): void { 101 | transformCallExpression(path); 102 | }, 103 | }); 104 | }, 105 | }, 106 | }; 107 | } 108 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@biomejs/biome/configuration_schema.json", 3 | "files": { 4 | "ignore": ["node_modules/**/*"] 5 | }, 6 | "vcs": { 7 | "useIgnoreFile": true 8 | }, 9 | "linter": { 10 | "enabled": true, 11 | "ignore": ["node_modules/**/*"], 12 | "rules": { 13 | "recommended": true, 14 | "a11y": { 15 | "noAriaHiddenOnFocusable": "off", 16 | "useIframeTitle": "warn", 17 | "useKeyWithClickEvents": "warn", 18 | "useKeyWithMouseEvents": "warn" 19 | }, 20 | "complexity": { 21 | "noForEach": "error", 22 | "noVoid": "off", 23 | "useOptionalChain": "warn", 24 | "useSimplifiedLogicExpression": "error" 25 | }, 26 | "correctness": { 27 | "noConstantMathMinMaxClamp": "error", 28 | "noInvalidNewBuiltin": "error", 29 | "noNewSymbol": "error", 30 | "noNodejsModules": "off", 31 | "noUndeclaredDependencies": "off", 32 | "noUndeclaredVariables": "error", 33 | "noUnusedFunctionParameters": "error", 34 | "noUnusedImports": "error", 35 | "noUnusedPrivateClassMembers": "error", 36 | "noUnusedVariables": "error", 37 | "useArrayLiterals": "error" 38 | }, 39 | "performance": { 40 | "noAccumulatingSpread": "error", 41 | "useTopLevelRegex": "error" 42 | }, 43 | "security": { 44 | "noGlobalEval": "off" 45 | }, 46 | "style": { 47 | "noArguments": "error", 48 | "noImplicitBoolean": "error", 49 | "noInferrableTypes": "error", 50 | "noNamespace": "error", 51 | "noNegationElse": "error", 52 | "noRestrictedGlobals": "error", 53 | "noShoutyConstants": "error", 54 | "noUnusedTemplateLiteral": "error", 55 | "noUselessElse": "error", 56 | "noVar": "error", 57 | "noYodaExpression": "error", 58 | "useAsConstAssertion": "error", 59 | "useBlockStatements": "error", 60 | "useCollapsedElseIf": "error", 61 | "useConsistentArrayType": "error", 62 | "useConsistentBuiltinInstantiation": "error", 63 | "useConst": "error", 64 | "useDefaultParameterLast": "error", 65 | "useEnumInitializers": "error", 66 | "useExponentiationOperator": "error", 67 | "useExportType": "error", 68 | "useFragmentSyntax": "error", 69 | "useForOf": "warn", 70 | "useImportType": "error", 71 | "useLiteralEnumMembers": "error", 72 | "useNodejsImportProtocol": "warn", 73 | "useNumberNamespace": "error", 74 | "useNumericLiterals": "error", 75 | "useSelfClosingElements": "error", 76 | "useShorthandArrayType": "error", 77 | "useShorthandAssign": "error", 78 | "useShorthandFunctionType": "warn", 79 | "useSingleCaseStatement": "error", 80 | "useSingleVarDeclarator": "error", 81 | "useTemplate": "off", 82 | "useWhile": "error" 83 | }, 84 | "suspicious": { 85 | "noConsoleLog": "warn", 86 | "noConstEnum": "off", 87 | "noDebugger": "off", 88 | "noEmptyBlockStatements": "error", 89 | "noExplicitAny": "warn", 90 | "noImplicitAnyLet": "off", 91 | "noMisrefactoredShorthandAssign": "off", 92 | "noSelfCompare": "off", 93 | "noSparseArray": "off", 94 | "noThenProperty": "warn", 95 | "useAwait": "error", 96 | "useErrorMessage": "error" 97 | } 98 | } 99 | }, 100 | "formatter": { 101 | "enabled": true, 102 | "ignore": ["node_modules/**/*"], 103 | "formatWithErrors": false, 104 | "indentWidth": 2, 105 | "indentStyle": "space", 106 | "lineEnding": "lf", 107 | "lineWidth": 80 108 | }, 109 | "organizeImports": { 110 | "enabled": true, 111 | "ignore": ["node_modules/**/*"] 112 | }, 113 | "javascript": { 114 | "formatter": { 115 | "enabled": true, 116 | "arrowParentheses": "asNeeded", 117 | "bracketSameLine": false, 118 | "bracketSpacing": true, 119 | "indentWidth": 2, 120 | "indentStyle": "space", 121 | "jsxQuoteStyle": "double", 122 | "lineEnding": "lf", 123 | "lineWidth": 80, 124 | "quoteProperties": "asNeeded", 125 | "quoteStyle": "single", 126 | "semicolons": "always", 127 | "trailingCommas": "all" 128 | }, 129 | "globals": [], 130 | "parser": { 131 | "unsafeParameterDecoratorsEnabled": true 132 | } 133 | }, 134 | "json": { 135 | "formatter": { 136 | "enabled": true, 137 | "indentWidth": 2, 138 | "indentStyle": "space", 139 | "lineEnding": "lf", 140 | "lineWidth": 80 141 | }, 142 | "parser": { 143 | "allowComments": false, 144 | "allowTrailingCommas": false 145 | } 146 | } 147 | } 148 | --------------------------------------------------------------------------------