Use CDN Components with a CLI Build?

2019-02-20 00:20发布

So, up until now, we've been building our Vue applications utilizing the standard script tag include for Vue (mostly so we can slowly transition from jQuery/Knockout-heavy apps), but as we start to convert our more complex apps, I can already see the maintenance issues we're going to have moving forward if we don't make the switch to the CLI build sooner than later.

Now, this isn't an issue for many of our apps, but since we adopted an "internal CDN" approach early on in our Vue apps, bundling everything in Webpack seems like somewhat of a step back in versatility. Right now we serve 4 files and then each route within our MVC app has its own associated Vue instance (ie: about.js) which controls the entire UI and its logic. Our CDN serves: 1. polyfills.js (for browser compatibility), 2. vendor.js (axios, moment.js and a few others), 3. vue.js (vue + vee-validate) and 4. components.js (our own custom UI component library).

In general, I don't care about 1-3. These can all be bundled in the webpack CLI build. It's #4 that I'm hung up on, as serving over the CDN has allowed us to push updates to all of our apps instantaneously, without running a new build. Right now, we only have 7 apps running a full Vue build, but our intention is to eventually convert all 80+ of our internal applications, plus several existing and new external applications over to Vue. If 30 of our apps are using one of our shared components and it needs to be updated to address any functional, accessibility, etc. concerns, that means we have to rebuild all 30 apps and push them, which isn't ideal at all.

Is there a way to continue to use the CDN build just for our components and bundle the rest as a SPA with Webpack?

Please note: This is not the same as referencing an external JS library, like jQuery. I'm trying to add Vue Components. If you load a library like this externally and then try to import the component via:

<ComponentName/>

Vue will give you a console error saying:

[Vue warn]: Unknown custom element: <ComponentName> - did you register the component correctly? For recursive components, make sure to provide the "name" option.

found in

    ---> <App> at src\App.vue
           <Root>

Simply adding it like so:

export default {
    name: 'app',
    components: {
       ComponentName
    }
}

Will return:

Uncaught ReferenceError: ComponentName is not defined

Because there's no import. But trying to import it also won't work, because it doesn't exist in the app, it's external.

3条回答
时光不老,我们不散
2楼-- · 2019-02-20 00:58

Well, technically you could use http-vue-loader which will load .vue components over HTTP. There are some limitations, though, and it's not recommended for production.

查看更多
女痞
3楼-- · 2019-02-20 01:12

You would like to manage your apps with a Vue webpack CLI build, but keeping the flexibility of having independent Vue components, i.e. served as separate files that are common to several apps / projects. That is an architecture that you describe with an "internal CDN".

As pointed out by @RandyCasburn, you could achieve the above objective by simply exposing Vue globally (typically loading it in a <script> from a CDN) and then loading your independent files that use Vue.component to register your components globally.

Reference: How to 'import' Vuejs component into index.html?

However, you would like to be able to develop these independent Vue Components as Single File Components (SFC).

I see basically 2 interesting solutions for the usage you describe:

  1. Filling the gap of creating the small build step tool to convert your SFC into a plain JS file, that can be loaded through a <script> tag and that registers the component globally with Vue.component.
  2. Instead of building a JS file that registers through Vue.component, make it expose the Vue Component configuration / options object, so that it can be locally registered and loaded asynchronously. This requires more work because you need to setup each consumer app webpack config to asynchronously load non-bundled chunks.

Option 1: Converting a Vue SFC to a single JS file with Vue.component

There is actually a recent template project that offers this feature:

https://github.com/RonnieSan/vue-browser-sfc

Template for build setup to compile Single File Components (.vue) into a standalone JS file for use in the browser

See also Compile a ".vue" component in browser, with JS only?

It uses webpack to convert your .vue file into a compiled JS file that registers the component globally with Vue.component.

You could also do a very similar process using Rollup instead of webpack, if leaner code is of interest to you. Use rollup-plugin-vue.

The advantage of this approach is that it is easy to understand, you just need to refactor your HTML to load Vue, then all your common components JS files. Even during development, these components should be globally available, so Vue runtime should not complain about "Unknown custom element", and in your consumer app, just do not import / declare them.

The drawback is that you have to load all these components before hand, whether you use them or not.

Option 2: Converting a Vue SFC to a single JS file as "async" options object

This one is far more interesting, but more complicated to set up right.

Like the previous solution, on the component side, you use a build step to convert the SFC .vue file, but you do not need an extra wrapper with Vue.component declaration. Just have webpack or Rollup wrap the component options object in an IIFE or UMD that registers the object in the global context.

For example your rollup.config.js file would look like:

import VuePlugin from 'rollup-plugin-vue'

export default {
  input: 'components/my-component.vue',
  output: {
    file: 'dist/my-component.js',
    name: 'MyComponent', // <= store the component on `window.MyComponent`
    format: 'umd',
  },
  plugins: [VuePlugin()]
}

Then on your consumer apps, you still import() and declare your components, but you need to setup the webpack configuration so it does not bundle the files in the app, but asynchronously loads them from the network. For example:

components: {
  'my-component': () => import('my-component'),
},

For that you can use dynamic-cdn-webpack-plugin, but you have to provide your own resolver function, because the default module-to-cdn uses its own modules list that is not configurable.

You list your components in dynamic-cdn-webpack-plugin's only array option, provide a resolver that mimics module-to-cdn but uses your own list. For example:

const modules = {
  'my-component': {
    var: 'MyComponent', // <= name under which the component will be stored.
    versions: {
      '*': {
        development: 'http://localhost:8080/my-cdn/my-component.js',
        production : 'http://localhost:8080/my-cdn/my-component.min.js',
      },
    },
  },
};

For this scheme to work, you also have to list your components as modules in your package.json dependencies, possibly with a dummy local path as version spec, to make sure you do not try to load them from npm registry.

The advantage is that you do not have to touch your HTML files, the async loading is directly managed by your apps. You can bundle Vue and do not care about the loading order / sequence. CDN components are loaded on demand when actually needed.

The drawback is that it requires a more complex webpack config for all your consumer apps, but it is totally manageable.

查看更多
\"骚年 ilove
4楼-- · 2019-02-20 01:18

Have you tried having multiple entry points configured in your webpack.config.js? You can then use one entry point to load just your components and the other for everything else. I believe the example lines below from the webpack website should work.

{
  entry: {
    app: './src/app.js', //Exports to /dist/app.js
    search: './src/components.js' //Exports to /dist/components.js
  },
  output: {
    filename: '[name].js',  //[name] is replaced with the 
                            //key names in the entry option(app & search) 
    path: __dirname + '/dist'
  }
}

Then you create a file /src/app.js which imports your items 1-3 and /src/components.js that imports your .vue files.

查看更多
登录 后发表回答