diff --git a/index.ts b/index.ts index 40e51dc..fd7c118 100644 --- a/index.ts +++ b/index.ts @@ -129,11 +129,14 @@ export default function sveltePlugin(options?: esbuildSvelteOptions): Plugin { //main loader build.onLoad({ filter: svelteFilter }, async (args) => { + let cachedFile = null; + let previousWatchFiles: string[] = []; + // if told to use the cache, check if it contains the file, // and if the modified time is not greater than the time when it was cached // if so, return the cached data if (options?.cache === true && fileCache.has(args.path)) { - const cachedFile = fileCache.get(args.path) || { + cachedFile = fileCache.get(args.path) || { dependencies: new Map(), data: null, }; // should never hit the null b/c of has check @@ -177,13 +180,26 @@ export default function sveltePlugin(options?: esbuildSvelteOptions): Plugin { //do preprocessor stuff if it exists if (options?.preprocess) { - let preprocessResult = await preprocess( - originalSource, - options.preprocess, - { - filename, + let preprocessResult = null; + + try { + preprocessResult = await preprocess( + originalSource, + options.preprocess, + { + filename, + } + ); + } + catch(e: any) { + // if preprocess failed there are chances that an external dependency caused exception + // to avoid stop watching those files, we keep the previous dependencies if available + if (build.initialOptions.watch && cachedFile) { + previousWatchFiles = Array.from(cachedFile.dependencies.keys()); } - ); + throw e; + } + if (preprocessResult.map) { // normalize the sourcemap 'source' entrys to all match if they are the same file // needed because of differing handling of file names in preprocessors @@ -262,7 +278,7 @@ export default function sveltePlugin(options?: esbuildSvelteOptions): Plugin { return result; } catch (e: any) { - return { errors: [convertMessage(e)] }; + return { errors: [convertMessage(e)], watchFiles: previousWatchFiles }; } }); diff --git a/test/fixtures/watch-preprocessing/entry.js b/test/fixtures/watch-preprocessing/entry.js new file mode 100644 index 0000000..5b4c482 --- /dev/null +++ b/test/fixtures/watch-preprocessing/entry.js @@ -0,0 +1,5 @@ +import Test from "./external-styles.svelte"; + +new Test({ + target: document.body, +}); diff --git a/test/fixtures/watch-preprocessing/external-styles.svelte b/test/fixtures/watch-preprocessing/external-styles.svelte new file mode 100644 index 0000000..08995e7 --- /dev/null +++ b/test/fixtures/watch-preprocessing/external-styles.svelte @@ -0,0 +1,7 @@ + + +
+

Hello World

+
diff --git a/test/fixtures/watch-preprocessing/external.scss b/test/fixtures/watch-preprocessing/external.scss new file mode 100644 index 0000000..7cd741e --- /dev/null +++ b/test/fixtures/watch-preprocessing/external.scss @@ -0,0 +1 @@ +p { color: red; } \ No newline at end of file diff --git a/test/watchPreprocessingTest.mjs b/test/watchPreprocessingTest.mjs new file mode 100644 index 0000000..e4ada32 --- /dev/null +++ b/test/watchPreprocessingTest.mjs @@ -0,0 +1,75 @@ +import { test } from "uvu"; +import * as assert from "uvu/assert"; +import { writeFileSync } from "fs"; +import { fileURLToPath } from "url"; +import { dirname } from "path"; +import { build as _build } from "esbuild"; +import { sass } from "svelte-preprocess-sass"; +import sveltePlugin from "../dist/index.mjs"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +test("Watch and build while preprocess of external dependency succeed and fails", async () => { + + function _createDeferred() { + let resolve = null; + let reject = null; + const promise = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); + promise.forceResolve = resolve; + promise.forceReject = reject; + return promise; + } + + let count = 0; + const firstRebuild = _createDeferred(); + const secondRebuild = _createDeferred(); + + //more advanced + const results = await _build({ + entryPoints: ["./test/fixtures/watch-preprocessing/entry.js"], + outdir: "../example/dist", + format: "esm", + minify: true, + bundle: true, + splitting: true, + write: false, //Don't write anywhere + sourcemap: "inline", + plugins: [ + sveltePlugin({ + preprocess: { + style: sass(), + }, + }), + ], + watch: { + onRebuild(err, result) { + count++; + if (count === 1) { + firstRebuild.forceResolve(err); + } + else if (count === 2) { + secondRebuild.forceResolve(result); + } + }, + }, + }); + + // write external scss with invalid syntax + writeFileSync(`${__dirname}/fixtures/watch-preprocessing/external.scss`, 'p { color: red; }!$%^&*()@$%^@@'); + const firstRebuildResult = await firstRebuild; + assert.ok(firstRebuildResult instanceof Error, 'First build did not fail'); + + // write external scss with valid syntax again + writeFileSync(`${__dirname}/fixtures/watch-preprocessing/external.scss`, 'p { color: red; }'); + const secondRebuildResult = await secondRebuild; + assert.ok(secondRebuildResult.errors.length === 0, "Second build fail"); + + // stop watching + results.stop(); +}); + +test.run();