embroider-build / ember-auto-import

Zero config import from npm packages
Other
360 stars 109 forks source link

Dynamically import CSS #261

Closed boris-petrov closed 3 years ago

boris-petrov commented 4 years ago

I'm trying to achieve the same functionality as dynamically-importing JavaScript (in order to have a smaller bundle size). That is, I have a CSS file that I want to load on-demand. What I do right now is:

ember-cli-build.js:

new Funnel('node_modules/c3', { destDir: '/assets', files: ['c3.css'] }),

app code:

function importCss(url: string): void {
  const link = document.createElement('link');
  link.rel = 'stylesheet';
  link.type = 'text/css';
  link.href = url;
  document.getElementsByTagName('head')[0].appendChild(link);
}

importCss('/assets/c3.css')

Not the best code as you see. I mostly hate the part in ember-cli-build. I guess ember-auto-import can help mostly with it. I read this answer - point 2 seems like something similar to what I need but it doesn't work - I think the browser tries to parse the result file as JavaScript. This PR also doesn't do what I want.

Any suggestions as to what I could do or what should be developed in ember-auto-import in order to support that?

ef4 commented 4 years ago

I think #205 supports your use case, but you need to bring your own webpack config for it.

let app = new EmberApp(defaults, {
  autoImport: {
    webpack: {
      // In here you need to configure something like webpack's style-loader
    }
  }
});
boris-petrov commented 4 years ago

That PR suggests that the included CSS files will end up in vendor.js which I don’t want. I want them to be separate files, same as the JS chunk files created. And then, perhaps, have an easy way of importing them from the frontend (that’s not mandatory - the current way is almost OK).

ef4 commented 4 years ago

Whether the code ends up in vendor vs another bundle is determined by whether anybody is dynamically importing it. Try adding style-loader and do await import('./your/style.css'). I think it may just work.

boris-petrov commented 4 years ago

@ef4 - thanks for the support. So here is what I did:

...
module: {
  rules: [
    {
      test: /\.css$/i,
      use: [
        { loader: 'style-loader', options: { injectType: 'linkTag' } },
        'css-loader',
      ],
    },
  ],
},
...

This in the webpack part of autoImport in ember-cli-build. Then I have await import('c3/c3.css');.

The problem is that ember-auto-import (or something, not sure) generates a chunk.xxx.js file which contains the code from style-loader for injecting a link tag and the CSS from the file itself. That's the first problem - the CSS should not be there. The second issue is that then this link is inserted and its href is the whole CSS - i.e. a request is made to a URL which is the CSS contents of the file. I hope you understand what I mean.

If I remove injectType: 'linkTag' then it works fine - a style tag with the contents of the file is created.

So this kind of works for me, but perhaps you could take a look at the other use case (which I also prefer) - the style-loader to append a link tag which contains the link to the CSS file which is bundled somewhere... not sure how this all would work.

Thanks again!

ef4 commented 4 years ago

I think that falls squarely into picking a different webpack config. If there's an option to style-loader, or a replacement for style-loader, that does what you want, that would be the solution.

(All that said, I do want to add a default standardized CSS configuration for ember-auto-import, so that it follows the same behavior that will be in embroider. But that would be a future feature that we don't have right now.)

boris-petrov commented 4 years ago

@ef4 - I'm not sure I understand what you mean. My point was - I made it work "fine" with style-loader, however what I think is best (and style-loader allows it) is for the styles to be inserted as link tags. But this doesn't work when used in ember-auto-import. Check the largest paragraph of my previous message for an explanation why. If I'm not making sense, I could create a reproduction repo for you to check out. I believe that is a bug in ember-auto-import (or perhaps just something which wasn't supposed to work ever).

In any case, I managed to do what I wanted (or almost at least) so this issue can be closed if you want, or you could leave it open until you decide that the other part with the link tag is OK too.

Thank you!

ef4 commented 4 years ago

Oh, I see. So you already tried that config but we break it somehow. Understood now.

brunoocasali commented 4 years ago

Reading this thread I could reduce my build size by importing css dynamic, but now I'm facing some strange behaviour, @ef4 and @boris-petrov if you could help me with this, would be awesome...

This is my load css in a component.js:

  *loadScript() {
    yield import('flatpickr/dist/flatpickr.min.css');
    this.script = yield import('flatpickr').then(m => m.default);
  }
...
"css-loader": "3.6.0",
"ember-auto-import": "1.5.3",
"style-loader": "1.2.1",
...

My autoImport config:

    autoImport: {
      alias: {
        flatpickr: 'flatpickr/dist/flatpickr.min',
      },
      webpack: {
        module: {
// to make this work, i must for some reason I don't understand, duplicate this rule.
          rules: [
            {
              test: /\.css$/,
              use: ['style-loader', 'css-loader']
            },
            {
              test: /\.css$/,
              use: ['style-loader', 'css-loader']
            },
          ]
        },
      }
    }

With just one rule I got this error:

ERROR in ./node_modules/flatpickr/dist/flatpickr.min.css 1:0
Module parse failed: Unexpected token (1:0)
You may need an appropriate loader to handle this file type.
> .flatpickr-calendar{background:transparent;opacity:0;display:none;text-align:center;visibility:hidden;padding:0;-webkit-animation:none;animation:none;direction:ltr;border:0;font-size:14px;line-height:24px;border-radius:5px;position:absolute;width:307.875px;.......omitted........flatpickr-prev-month{/*
|       /*rtl:begin:ignore*/left:0;/*
|       /*rtl:end:ignore*/}/*
 @ /private/var/folders/f0/c6hyjkv52qzgm01qjspmhlqr0000gn/T/broccoli-14077ALI16vwbmhoj/cache-235-bundler/staging/app.js 26:75-173
 @ multi /private/var/folders/f0/c6hyjkv52qzgm01qjspmhlqr0000gn/T/broccoli-14077ALI16vwbmhoj/cache-235-bundler/staging/l.js /private/var/folders/f0/c6hyjkv52qzgm01qjspmhlqr0000gn/T/broBuild Error (Bundler)

Build Error (Bundler)

webpack returned errors to ember-auto-import

Is this a webpack, or css-loader/style-loader or ember-auto-import issue?

Thanks in advance ;)

boris-petrov commented 4 years ago

@brunoocasali - that's the webpack config we use:

{
  test: /\.css$/i,
  use: [
    {
      loader: 'style-loader',
    },
    'css-loader',
  ],
},

Works fine on our side. I guess it should for you too.

brunoocasali commented 4 years ago

Unfortunately @boris-petrov this not work too, I've got the same error I've reported above :/

RobbieTheWagner commented 4 years ago

@brunoocasali why not use the addon for flatpickr? https://github.com/shipshapecode/ember-flatpickr

brunoocasali commented 4 years ago

In my current knowledge @rwwagner90 any addon I add to my ember app, will be bundled and carried all over the place even if the user does not use it in every route it load, am I right? (If I misunderstood please show me the light!)

The ember-flatpicker can do something like that?

I really wanted to give the users only what they need only when they want it!

wagenet commented 4 years ago

I had success with https://webpack.js.org/plugins/mini-css-extract-plugin/

pomm0 commented 3 years ago

To me this thread is a bit fragmented, so I wanted to give a summary for potential future readers about how I made it work. I needed it for mapbox-gl (at time of writing version 2.2.0) and wanted to lazily import js and css together:

Needed node packages (ember-auto-import @1.11.3):

npm install --save-dev css-loader style-loader

ember-cli-build.js:

autoImport: {
  alias: {
    'mapbox-gl-css': 'mapbox-gl/dist/mapbox-gl.css',
  },
  webpack: {
    module: {
      rules: [
        {
          test: /\.css$/i,
          use: [
            'style-loader',
            'css-loader',
          ],
        },
      ],
    },
  },
},

Where you want to use it (component.js or wherever):

const [mapboxModule] = await Promise.all([import('mapbox-gl'), import('mapbox-gl-css')])
const mapbox = mapboxModule.default;

There's nothing to do with the promise of import('mapbox-gl-css'), it will inject it as style tag to dom by itself.

The alias is not needed I guess, could also be import via import('mapbox-gl/dist/mapbox-gl.css') but I like the alias.

Edit:

As ef4 pointed out, for version 2.x of ember-auto-import no need to have the webpack part, so the ember-cli-build file would look something like that:

ember-cli-build.js:

autoImport: {
  alias: {
    'mapbox-gl-css': 'mapbox-gl/dist/mapbox-gl.css',
  },
},
ef4 commented 3 years ago

I would also point out that ember-auto-import 2.0 (which is in beta release right now) includes style-loader and css-loader by default, so in that case you would remove the whole webpack section of the config above.

boris-petrov commented 3 years ago

@ef4 - with style-loader now included by default, how do we add options to it? I have a custom insert function defined that I also want with ember-auto-import 2.0. How do I do that?

ef4 commented 3 years ago

We don't currently offer an option to extend the style-loader config, but that would be a reasonable thing to add if you want to make a PR.

ef4 commented 3 years ago

I think this is all resolved between ember-auto-import 2.0 and #408.