mistlog/svelte-draft-template

Race condition between TypeScript and Rollup

Closed this issue · 8 comments

Issue

There is a race condition on first run. App.svelte cannot be resolved on first run of the dev script, yet can be resolved on the second run (or upon saving App.tsx). I guess this is because main.js refers to App.Svelte, yet TypeScript hasn't transpiled App.tsx to App.svelte yet.

Installation

git clone https://github.com/mistlog/svelte-draft-template.git
cd svelte-draft-template
npm install

Running the dev script for the first time ❌

npm run dev
> svelte-app@1.0.0 dev /Users/jwork/Documents/git/svelte-draft-template
> concurrently "npm run transcribe:watch" "rollup -c -w"

[0] 
[0] > svelte-app@1.0.0 transcribe:watch /Users/jwork/Documents/git/svelte-draft-template
[0] > sd ./src -w
[0] 
[1] rollup v1.30.1
[1] bundles src/main.js → public/build/bundle.js...
[1] [!] Error: Could not resolve './App.svelte' from src/main.js
[1] Error: Could not resolve './App.svelte' from src/main.js
[1]     at error (/Users/jwork/Documents/git/svelte-draft-template/node_modules/rollup/dist/shared/node-entry.js:5400:30)
[1]     at ModuleLoader.handleResolveId (/Users/jwork/Documents/git/svelte-draft-template/node_modules/rollup/dist/shared/node-entry.js:12389:24)
[1]     at ModuleLoader.<anonymous> (/Users/jwork/Documents/git/svelte-draft-template/node_modules/rollup/dist/shared/node-entry.js:12277:30)
[1]     at Generator.next (<anonymous>)
[1]     at fulfilled (/Users/jwork/Documents/git/svelte-draft-template/node_modules/rollup/dist/shared/node-entry.js:38:28)
[1] 

Running the transcribe:watch script subsequently ✅

npm run transcribe:watch

> svelte-app@1.0.0 transcribe:watch /Users/jwork/Documents/git/svelte-draft-template
> sd ./src -w

Running the rollup -c -w script subsequently ✅

node_modules/.bin/rollup -c -w

  Your application is ready~! 🚀

  - Local:      http://localhost:5000

────────────────── LOGS ──────────────────

  [15:46:23] 200 ─ 4.33ms ─ /
  [15:46:23] 200 ─ 1.63ms ─ /global.css
  [15:46:23] 200 ─ 1.44ms ─ /build/bundle.css
  [15:46:23] 200 ─ 1.78ms ─ /build/bundle.js
  [15:46:23] 200 ─ 14.93ms ─ /favicon.png

Running the dev script subsequently once more ✅

npm run dev

  Your application is ready~! 🚀

  - Local:      http://localhost:5000

────────────────── LOGS ──────────────────

  [15:46:23] 200 ─ 4.33ms ─ /
  [15:46:23] 200 ─ 1.63ms ─ /global.css
  [15:46:23] 200 ─ 1.44ms ─ /build/bundle.css
  [15:46:23] 200 ─ 1.78ms ─ /build/bundle.js
  [15:46:23] 200 ─ 14.93ms ─ /favicon.png

Solution

In Webpack, in each rule, it is possible to provide an array of loaders to define an ordered pipeline by which to process files during the bundling process. It is quite common to process from SCSS -> CSS before bundling, for example. I'm sure Rollup will have an equivalent API allowing the TypeScript Compiler (or Babel) to be used to watch .tsx files and process them to .svelte files (ideally without emitting them to disk) without having to deal with a race condition between rollup and TypeScript. What do you think about this approach?

Thanks for pointing this out and I'm sorry that I didn't notice this problem.

I think this approach is great and it looks more professional to avoid intermediate files. After some search, it seems that rollup-plugin-svelte provides preprocess customization, I'm not familiar with bundling, so I don't know which is better(write a loader or plugin from scratch or use rollup-plugin-svelte). Do you have interest in solving this problem? Or any suggestions on that so that we can solve it with best practice.

I'm not familiar with bundling

I'm not greatly familiar myself – and I don't know a thing about Rollup, unfortunately! I'm more familiar with Webpack (but even then, I don't know anything about writing loaders and plugins).

Do you have interest in solving this problem?

I don't think I have enough time, regrettably! But I am interested in the project and could certainly advise.

For Webpack

It looks like there's a Webpack template, which makes use of a Webpack loader, svelte-loader.

If it were Webpack, I think it would involve setting up a rule like this:

  1. Test for /\.tsx$/.
  2. Convert TSX to Svelte. I see you've made svelte-draft for this. It would need to be made into a Webpack loader (somehow – I have no experience here either). Or there might be a way to do it as a babel plugin in combination with awesome-typescript-loader (as I do here to convert TSX to JS).
  3. Then convert Svelte to JS via svelte-loader.

For Rollup

If it were Rollup, I'm sure the process is similar. I guess:

  1. Test for /\.tsx$/.
  2. Convert TSX to Svelte via turning svelte-draft into a Rollup plugin (if "plugin" is the right word), if it's not already.
  3. Then convert Svelte to JS via rollup-plugin-svelte.

I'm not familiar with bundling, so I don't know which is better (write a loader or plugin from scratch or use rollup-plugin-svelte).

I think rollup-plugin-svelte would still be used as-is, but that would be a downstream step. The remaining task is to make svelte-draft do its logic as a rollup plugin to feed into it.

Thanks for your detailed reply!
I will try to solve it as soon as possible!

I've thought of a starting point for a proof-of-concept.

Add @rollup/plugin-typescript into your rollup config by adding typescript() into your plugins array (e.g. as demonstrated here). If I'm understanding the order of plugin execution correctly, you'd place it before rollup-plugin-svelte in the array of plugins. Now any .ts, .d.ts, and .tsx files (which are the default include options) will be resolved by Rollup and fed into the TypeScript plugin.

Next, create a transformer plugin like this:

import { createFilter } from 'rollup-pluginutils';

export default function myPlugin ( options = {} ) {
  const filter = createFilter( options.include, options.exclude );

  return {
    transform ( code, id ) {
      if ( !filter( id ) ) return;

      // proceed with the transformation...
      return {
        code: generatedCode,
        map: generatedSourceMap
      };
    }
  };
}

createFilter() is documented here; you'll have to find some way to sub-filter for .tsx files within the list of included files (as a later step, you can think about .jsx files too, but let's focus on this pipeline first).

The transformation should then perform whatever svelte-draft is doing at present. Note that rollup's plugin conventions state that methods should be asynchronous where possible, though you can start with sync if it's easier for a proof-of-concept

Here is what @rollup/plugin-typescript's own transformer function looks like, if it's any help:

https://github.com/rollup/plugins/blob/master/packages/typescript/src/outputToRollupTransformation.ts#L20

I'm not sure how easy it will be to provide source maps, but they can possibly be left undefined for a proof-of-concept.

Unsolved question: Although this approach achieves the transform from TSX -> Svelte, I'm not sure how you'd get the Svelte plugin to resolve this module as it's only resolving .svelte files. Maybe if you told the Svelte plugin to include .tsx files, it would succesfully take in the transformed .tsx because it's after the TypeScript plugin in the execution order?

Hope this helps..!

Thanks for the material you provided, it's definitely helpful! It's a concrete starting point and I will have a try.

For unsolved question, it seems that rollup-plugin-svelte receives options extensions, maybe we can specify .tsx and the plugin will treat it as .svelte file and compile.

I will sync my progress latter.


The pipeline is clear now. Add extensions field in rollup.config.js as follows:

svelte({
    ...
    extensions: ["./src/**/*.tsx"],
    ...
}),

Then svelte will find file ends with .tsx and compile it just as .svelte.

Then svelte will find file ends with .tsx and compile it just as .svelte.

The next question is: If you place rollup-plugin-svelte-draft (tentative name) before rollup-plugin-svelte in the plugins array, and add ./src/**/*.tsx as a specified extension to rollup-plugin-svelte, does that mean that all .tsx files resolved by Rollup will have been pre-processed by rollup-plugin-svelte-draft before passing onto rollup-plugin-svelte?

does that mean that all .tsx files resolved by Rollup will have been pre-processed by rollup-plugin-svelte-draft before passing onto rollup-plugin-svelte?

Yes, the order of plugins is exactly the order files get processed.


I'm sorry that I made a mistake:

svelte({
    ...
    extensions: ["./src/**/*.tsx"],
    ...
}),

the extensions should be [".tsx"] and after we add rollup-plugin-svelte-draft, it would be:

import SvelteDraft from "rollup-plugin-svelte-draft";
...
plugins: [
    SvelteDraft({include:["./src/**/*.tsx"]}),
    svelte({
	extensions: [".tsx"]
	...
}),

I'm working on such a plugin. hope I can finish it today! I will link repo of it later.

Done! Thanks for your help again!
The repo of plugin is: https://github.com/mistlog/rollup-plugin-svelte-draft.