question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Question/proposal: `{{importSync}}`

See original GitHub issue

Disclaimer: my mental model of Embroider’s build stages is too hazy still to be certain whether this is a viable idea, but if it’s both technically possible and sound from a design perspective, I’d be happy to help drive it forward.

Background

I’ve been mulling on the future of ember-css-modules in an Embroider-native world off and on for a long while. The work that addon does breaks down into three broad components:

  1. rewriting class names in component-local CSS files to make them unique per-file before adding their content to the application’s stylesheet(s)
  2. exposing a mapping from the original name to the rewritten one that’s available at runtime, importable from the location of the original CSS file
  3. rewriting local-class="original-name" attributes in templates to ultimately produce class="rewritten-name" at runtime

The first two steps can be easily achieved in the Embroider/v2 addon world by enabling CSS Modules in your packager of choice, most of which either natively support the feature (like Webpack’s css-loader) or have a well-paved path for enabling it via a plugin (like rollup-plugin-postcss).

Step 3 is the only Ember-unique element, and it presents a particular problem in that it relies on an implicit relationship between the template and its corresponding stylesheet. In classic builds this is ok, as the mapping module will always be included in the build, and we’re then able to require() it at runtime. In Embroider, however, we have no simple way of statically declaring that the template requires access to the mapping module, so it may not be resolvable by its original name if it’s imported elsewhere, or it may be excluded from the build entirely otherwise.

Proposal

A template equivalent of importSync would allow one possible solution to the problem outlined above, along the lines of:

<div local-class="hello"></div>

{{! => }}

{{#let (importSync "./the-component.module.css") as |__styles__|}}
<div class="{{__styles__.default.hello}}"></div>
{{/let}}

If {{importSync}} were available, then such a transform would function in classic or Embroider builds, loose- or strict-mode templates, without needing to special case any combination thereof.

This would also provide a path toward shipping as a v2 addon, with instructions for setting up your packager and configuring the template transform yourself.

Other Use Cases

While the details of the motivation here are outlined in terms of the specific needs of the CSS modules template transform, strict-mode templates will push toward a more general need for template transforms to be able to add static dependencies to templates.

As an example, if history had played out differently and strict-mode templates had become commonplace before named blocks landed, ember-named-blocks-polyfill would have needed a way to add imports for the internal helpers it uses to emulate named blocks support. In principle this could be done for strict-mode templates by transforming the template itself and then adding the necessary imports to the containing JS/TS module, but it seems unfortunate that every such polyfill or other transform would have to build the machinery to handle that separately.

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:1
  • Comments:7 (6 by maintainers)

github_iconTop GitHub Comments

1reaction
ef4commented, Feb 25, 2022

Overall yes to this, but I think we should look for implementations that are more general than {{importSync}}.

The general problem is: now that templates have a clearer relationship to Javascript and we expect you to provide things from Javascript scope into templates, how can a template transform emit some Javascript?

Starting at Ember 3.28, templates can get access to lexically scoped values even in non-strict mode, which makes the compatibility story much easier. All existing templates can be safely rewritten to precompileTemplate() with a scope argument. We don’t need to wait for people to port to strict mode.

This could take the form of a new API available to AST transforms something like:

function boundValue(params: Params): PathExpression;

type Params = {
  src: string       // a Javascript expression that will be used to initialize a local variable in JS scope
  nameHint?: string // optional name to aid debuggability. The system will suffix it as necessary to avoid collisions.
} | {
  moduleSpecifier: string; // path from which to import a value
  name: string;                   // the name to import from that module. "default" and "*" have their usual special meanings. 
  nameHint?: string;
}

The goal here is to support both ways of introducing a name into Javascript scope. So:

boundValue({ src: "1+1", nameHint: "total" })

would return a PathExpresion that your AST transform can insert into the template and that would get properly wired up like:

import { precompileTemplate } from "@ember/template-compilation";
let total = 1+1;
export default precompileTemplate('<div class={{total}} />', { scope: () => ({ total }) });

And similarly

boundValue({ moduleSpecifier: "./the-component.css", name: "default", nameHint: "styles" })

would result in something like:

import { precompileTemplate } from "@ember/template-compilation";
import styles from "./the-component.css";
export default precompileTemplate('<div class={{styles}} />', { scope: () => ({ styles }) });

I don’t know if PathExpression is exactly the right return type, but the general goal here is to let this new function return a name that you can use in your template to get access to the corresponding javascript value.

0reactions
dfreemancommented, Jun 30, 2022

That’s great news! Apologies for never following back up on this—I’ve ended up having to go down a number of different rabbitholes over the past few months at work to get us approaching a point where we can get to 3.28 and be in a position to take advantage of this functionality.

Read more comments on GitHub >

github_iconTop Results From Across the Web

ES6 modules aren't asynchronous, at least not as they are in ...
Now, there is a way to make them asynchronous called 'dynamic import'. In essence, you use import as a function in the global...
Read more >
Convert import() to synchronous - javascript - Stack Overflow
I'm trying to convert all of my node require() s into the import() statements, however, those are async and I'm having a bit...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found