Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make CSS assets async #1390

Merged
merged 10 commits into from
May 28, 2018
Merged

Make CSS assets async #1390

merged 10 commits into from
May 28, 2018

Conversation

fathyb
Copy link
Contributor

@fathyb fathyb commented May 18, 2018

Remove deasync from CSS assets.
Fixes #1331 and bring some performance improvements.

Status

  • Sass
  • Less
  • Stylus

@fathyb fathyb requested a review from DeMoorJasper May 18, 2018 13:06
@fathyb fathyb added 🐛 Bug 📝 WIP Work In Progress labels May 18, 2018
Copy link
Member

@DeMoorJasper DeMoorJasper left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about creating: Resolver.resolveSync inside the Resolver, that would remove all deasync calls instantly (except for get package in Asset.js, but that's just for backwards compatibility)?

The changes in sass resolver is a good edit though, as it's async anyway it doesn't need resolveSync.

@fathyb
Copy link
Contributor Author

fathyb commented May 18, 2018

@DeMoorJasper oh yeah, I was thinking the same. What do you think of making three classes :

  • Resolver: the commons stuffs shared
  • AsyncResolver: async implementation
  • SyncResolver: sync implementation

I'm trying to find a good pattern where we could reuse maximum code and only have to implement fs stuff in the Resolver implementations.

What do you think of implementing a Thenable interface for each resolver :

interface Thenable<T> {
  then<U>(
    do: (v: T) => (Thenable<U> | U),
    catch: (err: Error) => (Thenable<U> | U)
  ): Thenable<U>
}

interface SyncThenable<T> extends Thenable<T> {
  get(): T

  then<U>(
    do: (v: T) => (SyncThenable<U> | U),
    catch: (err: Error) => (SyncThenable<U> | U)
  ): SyncThenable<U>

  static from(value: T): SyncThenable<T>
}

And in each resolver:

  • SyncResolver.js
class SyncResolver extends Resolver {
  stat(file) {
    return Thenable.from(fs.statSync(file))
  }

  readFile(file) {
    return Thenable.from(fs.readFileSync(file))
  }

  resolve(input, parent) {
    // Returns the sync result of the Thenable
    return super.resolve(input, parent).get()
  }
}
  • AsyncResolver.js
class AsyncResolver extends Resolver {
  stat(file) {
    return fsUtils.stat(file)
  }

  readFile(file) {
    return fsUtils.readFile(file)
  }
}

@DeMoorJasper
Copy link
Member

DeMoorJasper commented May 18, 2018

I think the resolver should contain them both as a function, so that in the bundler we don't have to create a second instance if we would ever need a synchronous version.

@devongovett
Copy link
Member

hmm this was originally added for Stylus, which requires the resolution be synchronous. Even if we switched to readFileSync etc. it would still block the event loop. I wanted to avoid having two completely separate implementations of the resolver for async and sync, and since this has the same effect it seemed ok to me. If you can figure a way to make the Stylus resolution happen asynchronously without too much hassle that would be better, but until then deasync seemed like the easiest way.

@DeMoorJasper
Copy link
Member

DeMoorJasper commented May 18, 2018

Hope you don't mind me committing to this branch but I found a way to make the less filemanager async and pushed it to this branch @fathyb

So apparently all that's left now is Stylus

@codecov-io
Copy link

codecov-io commented May 18, 2018

Codecov Report

Merging #1390 into master will increase coverage by 3.49%.
The diff coverage is 85.91%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1390      +/-   ##
==========================================
+ Coverage   85.01%   88.51%   +3.49%     
==========================================
  Files          79       80       +1     
  Lines        4391     4639     +248     
==========================================
+ Hits         3733     4106     +373     
+ Misses        658      533     -125
Impacted Files Coverage Δ
src/FSCache.js 98.24% <100%> (ø) ⬆️
src/utils/pipeSpawn.js 92% <100%> (-0.31%) ⬇️
src/assets/SASSAsset.js 79.1% <60%> (-12.13%) ⬇️
src/assets/LESSAsset.js 92.53% <85.71%> (-0.52%) ⬇️
src/assets/StylusAsset.js 91.73% <92.5%> (+4.67%) ⬆️
src/utils/syncPromise.js 57.89% <0%> (-42.11%) ⬇️
src/utils/loadPlugins.js 64.28% <0%> (-26.2%) ⬇️
src/assets/PugAsset.js 88.57% <0%> (-11.43%) ⬇️
src/utils/PromiseQueue.js 90.78% <0%> (-3.45%) ⬇️
... and 36 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 08f9389...c754250. Read the comment docs.

@fathyb
Copy link
Contributor Author

fathyb commented May 18, 2018

Awesome @DeMoorJasper, I don't mind at all, thanks!

For Stylus I think I found a way to make it async using a double-traversal :

  • Parse the Stylus AST using stylus.Parser
  • Use a stylus.Visitor to traverse nodes and collect dependencies
  • pre-resolve and await dependencies promises
  • Call the custom stylus.Evaluator using the dependencies we already have resolved

@fathyb fathyb changed the title [WIP] Remove deasync Remove deasync May 18, 2018
@fathyb fathyb removed the 📝 WIP Work In Progress label May 18, 2018
@fathyb
Copy link
Contributor Author

fathyb commented May 18, 2018

Just pushed an async StylusAsset, no need for a sync Resolver anymore 😄

@fathyb fathyb changed the title Remove deasync Make CSS assets async May 18, 2018
I previously commit with `--no-verify`
@dobesv
Copy link
Contributor

dobesv commented May 19, 2018

I tried it out and it ran to completion for me.

@jpergler
Copy link

I can also confirm that the build finishes on previously problematic project.

cache: true,
filename: asset.name
};
let parser = new Parser(code, options);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unfortunately I think this will result in the code being parsed twice for each file: once here, and once when actually processing the code later. Do you think there is a way they could share the same AST?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested by putting some logs in Stylus' code and (ironically) cache: true broke the Parser cache (the cache is disabled if it isn't 'memory').

I fixed it and now it'll parse in getDependencies and use the cached AST in generate.

@DeMoorJasper DeMoorJasper merged commit 0d63879 into master May 28, 2018
@DeMoorJasper DeMoorJasper deleted the fix/remove-deasync branch May 28, 2018 08:58
@lysla lysla mentioned this pull request Jun 14, 2018
devongovett pushed a commit that referenced this pull request Oct 15, 2018
Remove `deasync` from CSS assets.
Fixes #1331 and bring some performance improvements.

#### Status

- [x] Sass
- [x] Less
- [x] Stylus
devongovett pushed a commit that referenced this pull request Oct 15, 2018
Remove `deasync` from CSS assets.
Fixes #1331 and bring some performance improvements.

#### Status

- [x] Sass
- [x] Less
- [x] Stylus
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants