diff --git a/packages/svelte/.gitignore b/packages/svelte/.gitignore index 662decc..e05da94 100644 --- a/packages/svelte/.gitignore +++ b/packages/svelte/.gitignore @@ -9,4 +9,5 @@ node_modules !.env.example vite.config.js.timestamp-* vite.config.ts.timestamp-* -coverage \ No newline at end of file +coverage +src/lib/tests/expected-transforms/**/_actual.js \ No newline at end of file diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 51673ea..9d63d7c 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -56,6 +56,7 @@ }, "dependencies": { "acorn": "^8.11.3", + "esm-env": "^1.1.4", "esrap": "^1.2.2", "zimmerframe": "^1.1.2" }, diff --git a/packages/svelte/src/lib/core.ts b/packages/svelte/src/lib/core.ts index eeeaace..3360eca 100644 --- a/packages/svelte/src/lib/core.ts +++ b/packages/svelte/src/lib/core.ts @@ -76,12 +76,14 @@ export class CancelationError extends Error { } } +export type TaskFunction = ( + args: TArgs, + utils: SheepdogUtils, +) => Promise | AsyncGenerator; + export function createTask( adapter: TaskAdapter, - gen_or_fun: ( - args: TArgs, - utils: SheepdogUtils, - ) => Promise | AsyncGenerator, + gen_or_fun: TaskFunction, options?: TaskOptions, ) { const handler_factory = handlers[options?.kind ?? 'default']; diff --git a/packages/svelte/src/lib/index.ts b/packages/svelte/src/lib/index.ts index 248378a..94ab216 100644 --- a/packages/svelte/src/lib/index.ts +++ b/packages/svelte/src/lib/index.ts @@ -1,6 +1,6 @@ // Reexport your entry components here -import { didCancel, timeout } from './utils'; +import { didCancel, timeout, transform } from './utils'; import { task, CancelationError } from './task.js'; export type { Task, SheepdogUtils, TaskInstance } from './task.js'; -export { task, CancelationError, didCancel, timeout }; +export { task, CancelationError, didCancel, timeout, transform }; diff --git a/packages/svelte/src/lib/task.ts b/packages/svelte/src/lib/task.ts index 2ca4850..129b7f2 100644 --- a/packages/svelte/src/lib/task.ts +++ b/packages/svelte/src/lib/task.ts @@ -1,6 +1,6 @@ import { onDestroy } from 'svelte'; import { writable } from 'svelte/store'; -import type { HandlerType, HandlersMap, SheepdogUtils, TaskOptions } from './core'; +import type { HandlerType, HandlersMap, SheepdogUtils, TaskOptions, TaskFunction } from './core'; import { CancelationError, createTask, handlers } from './core'; import type { ReadableWithGet, WritableWithGet } from './internal/helpers'; import { writable_with_get } from './internal/helpers'; @@ -22,10 +22,7 @@ export type TaskInstance = { }; function _task( - gen_or_fun: ( - args: TArgs, - utils: SheepdogUtils, - ) => Promise | AsyncGenerator, + gen_or_fun: TaskFunction, options?: TaskOptions, ) { const { subscribe, ...result } = writable({ diff --git a/packages/svelte/src/lib/tests/expected-transforms/wrapped-in-transform-renamed/code.js b/packages/svelte/src/lib/tests/expected-transforms/wrapped-in-transform-renamed/code.js new file mode 100644 index 0000000..8f861b6 --- /dev/null +++ b/packages/svelte/src/lib/tests/expected-transforms/wrapped-in-transform-renamed/code.js @@ -0,0 +1,143 @@ +import { transform as transform_renamed } from '@sheepdog/svelte/utils'; + +/** + * @param {()=>any} fn + */ +function transform(fn) { + return fn; +} + +/** + * @param {number} val + */ +function fn(val) { + return val; +} + +/** + * @type {Recordvoid>} + */ +const fns = {}; + +/** + * + * @param {TemplateStringsArray} quasi + * @param {string} strings + */ +function str(quasi, strings) {} + +export const task_fn = transform_renamed(async () => { + const assign = await Promise.resolve(); + let assignment = null; + assignment = await Promise.resolve(); + const array = [await Promise.resolve()]; + const sum = (await Promise.resolve(1)) + 2; + const sum2 = 2 + (await Promise.resolve(1)); + fns[await Promise.resolve(0)](); + const [array_destructure = await Promise.resolve()] = [undefined]; + const function_call = fn(await Promise.resolve(2)); + const conditional1 = (await Promise.resolve(true)) ? 1 : 2; + const conditional2 = true ? await Promise.resolve(1) : 2; + const conditional3 = false ? 1 : await Promise.resolve(2); + const member = { property: null }; + member[await Promise.resolve('property')] = await Promise.resolve(null); + const logical1 = (await Promise.resolve(null)) ?? 3; + const logical2 = null ?? (await Promise.resolve(null)); + const logical3 = null || (await Promise.resolve(null)); + const logical4 = (await Promise.resolve(null)) || null; + const logical5 = (await Promise.resolve(null)) && null; + const logical6 = true && (await Promise.resolve(null)); + const object_expression1 = { awaited: await Promise.resolve(2) }; + const object_expression2 = { awaited: { nested: await Promise.resolve(2) } }; + const object_expression3 = { awaited: { nested: [await Promise.resolve(2)] } }; + const tagged_template = str`something ${await Promise.resolve('')}`; + const template_literal = `something ${await Promise.resolve('')}`; + const unary = !(await Promise.resolve(true)); +}); + +const task_fn_no_export = transform_renamed(async () => { + const assign = await Promise.resolve(); + let assignment = null; + assignment = await Promise.resolve(); + const array = [await Promise.resolve()]; + const sum = (await Promise.resolve(1)) + 2; + const sum2 = 2 + (await Promise.resolve(1)); + fns[await Promise.resolve(0)](); + const [array_destructure = await Promise.resolve()] = [undefined]; + const function_call = fn(await Promise.resolve(2)); + const conditional1 = (await Promise.resolve(true)) ? 1 : 2; + const conditional2 = true ? await Promise.resolve(1) : 2; + const conditional3 = false ? 1 : await Promise.resolve(2); + const member = { property: null }; + member[await Promise.resolve('property')] = await Promise.resolve(null); + const logical1 = (await Promise.resolve(null)) ?? 3; + const logical2 = null ?? (await Promise.resolve(null)); + const logical3 = null || (await Promise.resolve(null)); + const logical4 = (await Promise.resolve(null)) || null; + const logical5 = (await Promise.resolve(null)) && null; + const logical6 = true && (await Promise.resolve(null)); + const object_expression1 = { awaited: await Promise.resolve(2) }; + const object_expression2 = { awaited: { nested: await Promise.resolve(2) } }; + const object_expression3 = { awaited: { nested: [await Promise.resolve(2)] } }; + const tagged_template = str`something ${await Promise.resolve('')}`; + const template_literal = `something ${await Promise.resolve('')}`; + const unary = !(await Promise.resolve(true)); +}); + +async function with_no_transform() { + const assign = await Promise.resolve(); + let assignment = null; + assignment = await Promise.resolve(); + const array = [await Promise.resolve()]; + const sum = (await Promise.resolve(1)) + 2; + const sum2 = 2 + (await Promise.resolve(1)); + fns[await Promise.resolve(0)](); + const [array_destructure = await Promise.resolve()] = [undefined]; + const function_call = fn(await Promise.resolve(2)); + const conditional1 = (await Promise.resolve(true)) ? 1 : 2; + const conditional2 = true ? await Promise.resolve(1) : 2; + const conditional3 = false ? 1 : await Promise.resolve(2); + const member = { property: null }; + member[await Promise.resolve('property')] = await Promise.resolve(null); + const logical1 = (await Promise.resolve(null)) ?? 3; + const logical2 = null ?? (await Promise.resolve(null)); + const logical3 = null || (await Promise.resolve(null)); + const logical4 = (await Promise.resolve(null)) || null; + const logical5 = (await Promise.resolve(null)) && null; + const logical6 = true && (await Promise.resolve(null)); + const object_expression1 = { awaited: await Promise.resolve(2) }; + const object_expression2 = { awaited: { nested: await Promise.resolve(2) } }; + const object_expression3 = { awaited: { nested: [await Promise.resolve(2)] } }; + const tagged_template = str`something ${await Promise.resolve('')}`; + const template_literal = `something ${await Promise.resolve('')}`; + const unary = !(await Promise.resolve(true)); +} + +const different_transform = transform(async () => { + const assign = await Promise.resolve(); + let assignment = null; + assignment = await Promise.resolve(); + const array = [await Promise.resolve()]; + const sum = (await Promise.resolve(1)) + 2; + const sum2 = 2 + (await Promise.resolve(1)); + fns[await Promise.resolve(0)](); + const [array_destructure = await Promise.resolve()] = [undefined]; + const function_call = fn(await Promise.resolve(2)); + const conditional1 = (await Promise.resolve(true)) ? 1 : 2; + const conditional2 = true ? await Promise.resolve(1) : 2; + const conditional3 = false ? 1 : await Promise.resolve(2); + const member = { property: null }; + member[await Promise.resolve('property')] = await Promise.resolve(null); + const logical1 = (await Promise.resolve(null)) ?? 3; + const logical2 = null ?? (await Promise.resolve(null)); + const logical3 = null || (await Promise.resolve(null)); + const logical4 = (await Promise.resolve(null)) || null; + const logical5 = (await Promise.resolve(null)) && null; + const logical6 = true && (await Promise.resolve(null)); + const object_expression1 = { awaited: await Promise.resolve(2) }; + const object_expression2 = { awaited: { nested: await Promise.resolve(2) } }; + const object_expression3 = { awaited: { nested: [await Promise.resolve(2)] } }; + const tagged_template = str`something ${await Promise.resolve('')}`; + const template_literal = `something ${await Promise.resolve('')}`; + const unary = !(await Promise.resolve(true)); +}); diff --git a/packages/svelte/src/lib/tests/expected-transforms/wrapped-in-transform-renamed/transform.js b/packages/svelte/src/lib/tests/expected-transforms/wrapped-in-transform-renamed/transform.js new file mode 100644 index 0000000..dd1df56 --- /dev/null +++ b/packages/svelte/src/lib/tests/expected-transforms/wrapped-in-transform-renamed/transform.js @@ -0,0 +1,193 @@ +import { transform as transform_renamed } from '@sheepdog/svelte/utils'; + +function transform(fn) { + return fn; +} + +function fn(val) { + return val; +} + +const fns = {}; + +function str(quasi, strings) {} + +export const task_fn = async function* () { + const assign = yield Promise.resolve(); + let assignment = null; + + assignment = yield Promise.resolve(); + + const array = [yield Promise.resolve()]; + const sum = (yield Promise.resolve(1)) + 2; + const sum2 = 2 + (yield Promise.resolve(1)); + + fns[yield Promise.resolve(0)](); + + const [ + array_destructure = yield Promise.resolve() + ] = [undefined]; + + const function_call = fn(yield Promise.resolve(2)); + const conditional1 = (yield Promise.resolve(true)) ? 1 : 2; + const conditional2 = true ? yield Promise.resolve(1) : 2; + const conditional3 = false ? 1 : yield Promise.resolve(2); + const member = { property: null }; + + member[yield Promise.resolve('property')] = yield Promise.resolve(null); + + const logical1 = (yield Promise.resolve(null)) ?? 3; + const logical2 = null ?? (yield Promise.resolve(null)); + const logical3 = null || (yield Promise.resolve(null)); + const logical4 = (yield Promise.resolve(null)) || null; + const logical5 = (yield Promise.resolve(null)) && null; + const logical6 = true && (yield Promise.resolve(null)); + const object_expression1 = { awaited: yield Promise.resolve(2) }; + + const object_expression2 = { + awaited: { nested: yield Promise.resolve(2) } + }; + + const object_expression3 = { + awaited: { nested: [yield Promise.resolve(2)] } + }; + + const tagged_template = str`something ${yield Promise.resolve('')}`; + const template_literal = `something ${yield Promise.resolve('')}`; + const unary = !(yield Promise.resolve(true)); +}; + +const task_fn_no_export = async function* () { + const assign = yield Promise.resolve(); + let assignment = null; + + assignment = yield Promise.resolve(); + + const array = [yield Promise.resolve()]; + const sum = (yield Promise.resolve(1)) + 2; + const sum2 = 2 + (yield Promise.resolve(1)); + + fns[yield Promise.resolve(0)](); + + const [ + array_destructure = yield Promise.resolve() + ] = [undefined]; + + const function_call = fn(yield Promise.resolve(2)); + const conditional1 = (yield Promise.resolve(true)) ? 1 : 2; + const conditional2 = true ? yield Promise.resolve(1) : 2; + const conditional3 = false ? 1 : yield Promise.resolve(2); + const member = { property: null }; + + member[yield Promise.resolve('property')] = yield Promise.resolve(null); + + const logical1 = (yield Promise.resolve(null)) ?? 3; + const logical2 = null ?? (yield Promise.resolve(null)); + const logical3 = null || (yield Promise.resolve(null)); + const logical4 = (yield Promise.resolve(null)) || null; + const logical5 = (yield Promise.resolve(null)) && null; + const logical6 = true && (yield Promise.resolve(null)); + const object_expression1 = { awaited: yield Promise.resolve(2) }; + + const object_expression2 = { + awaited: { nested: yield Promise.resolve(2) } + }; + + const object_expression3 = { + awaited: { nested: [yield Promise.resolve(2)] } + }; + + const tagged_template = str`something ${yield Promise.resolve('')}`; + const template_literal = `something ${yield Promise.resolve('')}`; + const unary = !(yield Promise.resolve(true)); +}; + +async function with_no_transform() { + const assign = await Promise.resolve(); + let assignment = null; + + assignment = await Promise.resolve(); + + const array = [await Promise.resolve()]; + const sum = await Promise.resolve(1) + 2; + const sum2 = 2 + await Promise.resolve(1); + + fns[await Promise.resolve(0)](); + + const [ + array_destructure = await Promise.resolve() + ] = [undefined]; + + const function_call = fn(await Promise.resolve(2)); + const conditional1 = await Promise.resolve(true) ? 1 : 2; + const conditional2 = true ? await Promise.resolve(1) : 2; + const conditional3 = false ? 1 : await Promise.resolve(2); + const member = { property: null }; + + member[await Promise.resolve('property')] = await Promise.resolve(null); + + const logical1 = await Promise.resolve(null) ?? 3; + const logical2 = null ?? await Promise.resolve(null); + const logical3 = null || await Promise.resolve(null); + const logical4 = await Promise.resolve(null) || null; + const logical5 = await Promise.resolve(null) && null; + const logical6 = true && await Promise.resolve(null); + const object_expression1 = { awaited: await Promise.resolve(2) }; + + const object_expression2 = { + awaited: { nested: await Promise.resolve(2) } + }; + + const object_expression3 = { + awaited: { nested: [await Promise.resolve(2)] } + }; + + const tagged_template = str`something ${await Promise.resolve('')}`; + const template_literal = `something ${await Promise.resolve('')}`; + const unary = !await Promise.resolve(true); +} + +const different_transform = transform(async () => { + const assign = await Promise.resolve(); + let assignment = null; + + assignment = await Promise.resolve(); + + const array = [await Promise.resolve()]; + const sum = await Promise.resolve(1) + 2; + const sum2 = 2 + await Promise.resolve(1); + + fns[await Promise.resolve(0)](); + + const [ + array_destructure = await Promise.resolve() + ] = [undefined]; + + const function_call = fn(await Promise.resolve(2)); + const conditional1 = await Promise.resolve(true) ? 1 : 2; + const conditional2 = true ? await Promise.resolve(1) : 2; + const conditional3 = false ? 1 : await Promise.resolve(2); + const member = { property: null }; + + member[await Promise.resolve('property')] = await Promise.resolve(null); + + const logical1 = await Promise.resolve(null) ?? 3; + const logical2 = null ?? await Promise.resolve(null); + const logical3 = null || await Promise.resolve(null); + const logical4 = await Promise.resolve(null) || null; + const logical5 = await Promise.resolve(null) && null; + const logical6 = true && await Promise.resolve(null); + const object_expression1 = { awaited: await Promise.resolve(2) }; + + const object_expression2 = { + awaited: { nested: await Promise.resolve(2) } + }; + + const object_expression3 = { + awaited: { nested: [await Promise.resolve(2)] } + }; + + const tagged_template = str`something ${await Promise.resolve('')}`; + const template_literal = `something ${await Promise.resolve('')}`; + const unary = !await Promise.resolve(true); +}); \ No newline at end of file diff --git a/packages/svelte/src/lib/tests/expected-transforms/wrapped-in-transform/code.js b/packages/svelte/src/lib/tests/expected-transforms/wrapped-in-transform/code.js new file mode 100644 index 0000000..cff872a --- /dev/null +++ b/packages/svelte/src/lib/tests/expected-transforms/wrapped-in-transform/code.js @@ -0,0 +1,107 @@ +import { transform } from '@sheepdog/svelte/utils'; + +/** + * @param {number} val + */ +function fn(val) { + return val; +} + +/** + * @type {Recordvoid>} + */ +const fns = {}; + +/** + * + * @param {TemplateStringsArray} quasi + * @param {string} strings + */ +function str(quasi, strings) {} + +export const task_fn = transform(async () => { + const assign = await Promise.resolve(); + let assignment = null; + assignment = await Promise.resolve(); + const array = [await Promise.resolve()]; + const sum = (await Promise.resolve(1)) + 2; + const sum2 = 2 + (await Promise.resolve(1)); + fns[await Promise.resolve(0)](); + const [array_destructure = await Promise.resolve()] = [undefined]; + const function_call = fn(await Promise.resolve(2)); + const conditional1 = (await Promise.resolve(true)) ? 1 : 2; + const conditional2 = true ? await Promise.resolve(1) : 2; + const conditional3 = false ? 1 : await Promise.resolve(2); + const member = { property: null }; + member[await Promise.resolve('property')] = await Promise.resolve(null); + const logical1 = (await Promise.resolve(null)) ?? 3; + const logical2 = null ?? (await Promise.resolve(null)); + const logical3 = null || (await Promise.resolve(null)); + const logical4 = (await Promise.resolve(null)) || null; + const logical5 = (await Promise.resolve(null)) && null; + const logical6 = true && (await Promise.resolve(null)); + const object_expression1 = { awaited: await Promise.resolve(2) }; + const object_expression2 = { awaited: { nested: await Promise.resolve(2) } }; + const object_expression3 = { awaited: { nested: [await Promise.resolve(2)] } }; + const tagged_template = str`something ${await Promise.resolve('')}`; + const template_literal = `something ${await Promise.resolve('')}`; + const unary = !(await Promise.resolve(true)); +}); + +const task_fn_no_export = transform(async () => { + const assign = await Promise.resolve(); + let assignment = null; + assignment = await Promise.resolve(); + const array = [await Promise.resolve()]; + const sum = (await Promise.resolve(1)) + 2; + const sum2 = 2 + (await Promise.resolve(1)); + fns[await Promise.resolve(0)](); + const [array_destructure = await Promise.resolve()] = [undefined]; + const function_call = fn(await Promise.resolve(2)); + const conditional1 = (await Promise.resolve(true)) ? 1 : 2; + const conditional2 = true ? await Promise.resolve(1) : 2; + const conditional3 = false ? 1 : await Promise.resolve(2); + const member = { property: null }; + member[await Promise.resolve('property')] = await Promise.resolve(null); + const logical1 = (await Promise.resolve(null)) ?? 3; + const logical2 = null ?? (await Promise.resolve(null)); + const logical3 = null || (await Promise.resolve(null)); + const logical4 = (await Promise.resolve(null)) || null; + const logical5 = (await Promise.resolve(null)) && null; + const logical6 = true && (await Promise.resolve(null)); + const object_expression1 = { awaited: await Promise.resolve(2) }; + const object_expression2 = { awaited: { nested: await Promise.resolve(2) } }; + const object_expression3 = { awaited: { nested: [await Promise.resolve(2)] } }; + const tagged_template = str`something ${await Promise.resolve('')}`; + const template_literal = `something ${await Promise.resolve('')}`; + const unary = !(await Promise.resolve(true)); +}); + +async function with_no_transform() { + const assign = await Promise.resolve(); + let assignment = null; + assignment = await Promise.resolve(); + const array = [await Promise.resolve()]; + const sum = (await Promise.resolve(1)) + 2; + const sum2 = 2 + (await Promise.resolve(1)); + fns[await Promise.resolve(0)](); + const [array_destructure = await Promise.resolve()] = [undefined]; + const function_call = fn(await Promise.resolve(2)); + const conditional1 = (await Promise.resolve(true)) ? 1 : 2; + const conditional2 = true ? await Promise.resolve(1) : 2; + const conditional3 = false ? 1 : await Promise.resolve(2); + const member = { property: null }; + member[await Promise.resolve('property')] = await Promise.resolve(null); + const logical1 = (await Promise.resolve(null)) ?? 3; + const logical2 = null ?? (await Promise.resolve(null)); + const logical3 = null || (await Promise.resolve(null)); + const logical4 = (await Promise.resolve(null)) || null; + const logical5 = (await Promise.resolve(null)) && null; + const logical6 = true && (await Promise.resolve(null)); + const object_expression1 = { awaited: await Promise.resolve(2) }; + const object_expression2 = { awaited: { nested: await Promise.resolve(2) } }; + const object_expression3 = { awaited: { nested: [await Promise.resolve(2)] } }; + const tagged_template = str`something ${await Promise.resolve('')}`; + const template_literal = `something ${await Promise.resolve('')}`; + const unary = !(await Promise.resolve(true)); +} diff --git a/packages/svelte/src/lib/tests/expected-transforms/wrapped-in-transform/transform.js b/packages/svelte/src/lib/tests/expected-transforms/wrapped-in-transform/transform.js new file mode 100644 index 0000000..897e99b --- /dev/null +++ b/packages/svelte/src/lib/tests/expected-transforms/wrapped-in-transform/transform.js @@ -0,0 +1,144 @@ +import { transform } from '@sheepdog/svelte/utils'; + +function fn(val) { + return val; +} + +const fns = {}; + +function str(quasi, strings) {} + +export const task_fn = async function* () { + const assign = yield Promise.resolve(); + let assignment = null; + + assignment = yield Promise.resolve(); + + const array = [yield Promise.resolve()]; + const sum = (yield Promise.resolve(1)) + 2; + const sum2 = 2 + (yield Promise.resolve(1)); + + fns[yield Promise.resolve(0)](); + + const [ + array_destructure = yield Promise.resolve() + ] = [undefined]; + + const function_call = fn(yield Promise.resolve(2)); + const conditional1 = (yield Promise.resolve(true)) ? 1 : 2; + const conditional2 = true ? yield Promise.resolve(1) : 2; + const conditional3 = false ? 1 : yield Promise.resolve(2); + const member = { property: null }; + + member[yield Promise.resolve('property')] = yield Promise.resolve(null); + + const logical1 = (yield Promise.resolve(null)) ?? 3; + const logical2 = null ?? (yield Promise.resolve(null)); + const logical3 = null || (yield Promise.resolve(null)); + const logical4 = (yield Promise.resolve(null)) || null; + const logical5 = (yield Promise.resolve(null)) && null; + const logical6 = true && (yield Promise.resolve(null)); + const object_expression1 = { awaited: yield Promise.resolve(2) }; + + const object_expression2 = { + awaited: { nested: yield Promise.resolve(2) } + }; + + const object_expression3 = { + awaited: { nested: [yield Promise.resolve(2)] } + }; + + const tagged_template = str`something ${yield Promise.resolve('')}`; + const template_literal = `something ${yield Promise.resolve('')}`; + const unary = !(yield Promise.resolve(true)); +}; + +const task_fn_no_export = async function* () { + const assign = yield Promise.resolve(); + let assignment = null; + + assignment = yield Promise.resolve(); + + const array = [yield Promise.resolve()]; + const sum = (yield Promise.resolve(1)) + 2; + const sum2 = 2 + (yield Promise.resolve(1)); + + fns[yield Promise.resolve(0)](); + + const [ + array_destructure = yield Promise.resolve() + ] = [undefined]; + + const function_call = fn(yield Promise.resolve(2)); + const conditional1 = (yield Promise.resolve(true)) ? 1 : 2; + const conditional2 = true ? yield Promise.resolve(1) : 2; + const conditional3 = false ? 1 : yield Promise.resolve(2); + const member = { property: null }; + + member[yield Promise.resolve('property')] = yield Promise.resolve(null); + + const logical1 = (yield Promise.resolve(null)) ?? 3; + const logical2 = null ?? (yield Promise.resolve(null)); + const logical3 = null || (yield Promise.resolve(null)); + const logical4 = (yield Promise.resolve(null)) || null; + const logical5 = (yield Promise.resolve(null)) && null; + const logical6 = true && (yield Promise.resolve(null)); + const object_expression1 = { awaited: yield Promise.resolve(2) }; + + const object_expression2 = { + awaited: { nested: yield Promise.resolve(2) } + }; + + const object_expression3 = { + awaited: { nested: [yield Promise.resolve(2)] } + }; + + const tagged_template = str`something ${yield Promise.resolve('')}`; + const template_literal = `something ${yield Promise.resolve('')}`; + const unary = !(yield Promise.resolve(true)); +}; + +async function with_no_transform() { + const assign = await Promise.resolve(); + let assignment = null; + + assignment = await Promise.resolve(); + + const array = [await Promise.resolve()]; + const sum = await Promise.resolve(1) + 2; + const sum2 = 2 + await Promise.resolve(1); + + fns[await Promise.resolve(0)](); + + const [ + array_destructure = await Promise.resolve() + ] = [undefined]; + + const function_call = fn(await Promise.resolve(2)); + const conditional1 = await Promise.resolve(true) ? 1 : 2; + const conditional2 = true ? await Promise.resolve(1) : 2; + const conditional3 = false ? 1 : await Promise.resolve(2); + const member = { property: null }; + + member[await Promise.resolve('property')] = await Promise.resolve(null); + + const logical1 = await Promise.resolve(null) ?? 3; + const logical2 = null ?? await Promise.resolve(null); + const logical3 = null || await Promise.resolve(null); + const logical4 = await Promise.resolve(null) || null; + const logical5 = await Promise.resolve(null) && null; + const logical6 = true && await Promise.resolve(null); + const object_expression1 = { awaited: await Promise.resolve(2) }; + + const object_expression2 = { + awaited: { nested: await Promise.resolve(2) } + }; + + const object_expression3 = { + awaited: { nested: [await Promise.resolve(2)] } + }; + + const tagged_template = str`something ${await Promise.resolve('')}`; + const template_literal = `something ${await Promise.resolve('')}`; + const unary = !await Promise.resolve(true); +} \ No newline at end of file diff --git a/packages/svelte/src/lib/tests/threeshake/index.test.ts b/packages/svelte/src/lib/tests/threeshake/index.test.ts index 16e1538..205916a 100644 --- a/packages/svelte/src/lib/tests/threeshake/index.test.ts +++ b/packages/svelte/src/lib/tests/threeshake/index.test.ts @@ -187,4 +187,54 @@ describe('threeshake', () => { " `); }); + + test('importing `transform` should treeshake the rest of the library', async () => { + const file = await does_it_shake(`import { transform } from '@sheepdog/svelte'; + + console.log(transform);`); + expect(file).toMatchInlineSnapshot(` + "function transform(fn) { + { + throw new Error("You are using the transform function without the vite plugin. Please add the \`asyncTransform\` plugin to your \`vite.config.ts\`"); + } + } + function rune_outside_svelte(rune) { + { + const error = new Error(\`rune_outside_svelte + The \\\`\${rune}\\\` rune is only available inside \\\`.svelte\\\` and \\\`.svelte.js/ts\\\` files\`); + error.name = "Svelte error"; + throw error; + } + } + { + let throw_rune_error = function(rune) { + if (!(rune in globalThis)) { + let value; + Object.defineProperty(globalThis, rune, { + configurable: true, + // eslint-disable-next-line getter-return + get: () => { + if (value !== void 0) { + return value; + } + rune_outside_svelte(rune); + }, + set: (v) => { + value = v; + } + }); + } + }; + var throw_rune_error2 = throw_rune_error; + throw_rune_error("$state"); + throw_rune_error("$effect"); + throw_rune_error("$derived"); + throw_rune_error("$inspect"); + throw_rune_error("$props"); + throw_rune_error("$bindable"); + } + console.log(transform); + " + `); + }); }); diff --git a/packages/svelte/src/lib/tests/vite.test.ts b/packages/svelte/src/lib/tests/vite.test.ts index edbd9a7..e27f638 100644 --- a/packages/svelte/src/lib/tests/vite.test.ts +++ b/packages/svelte/src/lib/tests/vite.test.ts @@ -1,10 +1,11 @@ import { asyncTransform } from '../vite'; import { describe, it, expect } from 'vitest'; +import { writeFile } from 'node:fs/promises'; -const plugin = asyncTransform(); +const plugin = await asyncTransform(); const expected_entries = Object.entries( - import.meta.glob('./expected-transforms/**/*.js', { as: 'raw', eager: true }), + import.meta.glob('./expected-transforms/**/(code|transform).js', { as: 'raw', eager: true }), ); const expected = new Map(); @@ -26,6 +27,10 @@ describe('sheepdog transform', () => { it.each([...expected.entries()])('%s', async (id, { code, transform }) => { // @ts-expect-error we don't have the correct `this` here but we are not using it const actual_transform = await plugin.transform(code, 'myfile.js'); + if (actual_transform) { + // write the actual transform to file for better debugging + writeFile(`./src/lib/tests/expected-transforms/${id}/_actual.js`, actual_transform.code); + } expect(actual_transform?.code).toBe(transform); }); }); diff --git a/packages/svelte/src/lib/utils.ts b/packages/svelte/src/lib/utils.ts index cafe717..2eafc3e 100644 --- a/packages/svelte/src/lib/utils.ts +++ b/packages/svelte/src/lib/utils.ts @@ -1,9 +1,31 @@ +import { DEV } from 'esm-env'; import { CancelationError } from './core'; +import type { TaskFunction } from './core'; +/** + * Utility method to know if a `perform` thrown because it was canceled + */ export const didCancel = (e: Error | CancelationError) => { return e instanceof CancelationError; }; +/** + * A Promise that will resolve after {ms} milliseconds + */ export async function timeout(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); } + +/** + * A function to mark your function to be transformable by the + * async transform vite plugin. It will error out in dev mode + * if you didn't add the plugin. + */ +export function transform(fn: T): T { + if (DEV) { + throw new Error( + 'You are using the transform function without the vite plugin. Please add the `asyncTransform` plugin to your `vite.config.ts`', + ); + } + return fn; +} diff --git a/packages/svelte/src/lib/vite.ts b/packages/svelte/src/lib/vite.ts index a031959..2bff4cb 100644 --- a/packages/svelte/src/lib/vite.ts +++ b/packages/svelte/src/lib/vite.ts @@ -6,10 +6,7 @@ import type { FunctionExpression, ImportDeclaration, } from 'acorn'; -import { parse } from 'acorn'; -import { print } from 'esrap'; import type { Plugin } from 'vite'; -import { walk } from 'zimmerframe'; type Nodes = | ImportDeclaration @@ -19,7 +16,11 @@ type Nodes = | BlockStatement | AwaitExpression; -export function asyncTransform() { +export async function asyncTransform() { + const { parse } = await import('acorn'); + const { print } = await import('esrap'); + const { walk } = await import('zimmerframe'); + return { name: 'sheepdog-async-transform', async transform(code, id) { @@ -29,14 +30,18 @@ export function asyncTransform() { locations: true, sourceType: 'module', }); - let fn_name: string; + let task_fn_name: string; + let transform_fn_name: string; // let's walk once to find the name (we were using a promise before but that's just messy) walk( ast as unknown as ImportDeclaration, {}, { ImportDeclaration(node) { - if (node.source.value === '@sheepdog/svelte') { + if ( + node.source.value === '@sheepdog/svelte' || + node.source.value === '@sheepdog/svelte/task' + ) { const task_fn = node.specifiers.find((specifier) => { return ( specifier.type === 'ImportSpecifier' && @@ -45,7 +50,18 @@ export function asyncTransform() { ); }); if (task_fn && task_fn.type === 'ImportSpecifier') { - fn_name = task_fn.local.name; + task_fn_name = task_fn.local.name; + } + } else if (node.source.value === '@sheepdog/svelte/utils') { + const transform_fn = node.specifiers.find((specifier) => { + return ( + specifier.type === 'ImportSpecifier' && + specifier.imported.type === 'Identifier' && + specifier.imported.name === 'transform' + ); + }); + if (transform_fn && transform_fn.type === 'ImportSpecifier') { + transform_fn_name = transform_fn.local.name; } } }, @@ -66,13 +82,15 @@ export function asyncTransform() { }, CallExpression(node, { state, next }) { let local_changed = false; + let task_arg: (typeof node)['arguments'][number] | undefined; if ( - (node.callee.type === 'Identifier' && node.callee.name === fn_name) || + (node.callee.type === 'Identifier' && + (node.callee.name === task_fn_name || node.callee.name === transform_fn_name)) || (node.callee.type === 'MemberExpression' && node.callee.object.type === 'Identifier' && - node.callee.object.name === fn_name) + node.callee.object.name === task_fn_name) ) { - const task_arg = node.arguments[0]; + task_arg = node.arguments[0]; if (task_arg && task_arg.type === 'ArrowFunctionExpression' && task_arg.async) { const to_change = task_arg as unknown as FunctionExpression; to_change.type = 'FunctionExpression'; @@ -96,6 +114,13 @@ export function asyncTransform() { if (!local_changed) { next(); } + if ( + task_arg && + node.callee.type === 'Identifier' && + node.callee.name === transform_fn_name + ) { + return task_arg as never; + } }, }, ) as unknown as typeof ast; @@ -109,7 +134,8 @@ export function asyncTransform() { }), }; } - } catch { + } catch (e) { + console.error(e); /** in case parsing fails */ } },