有没有一种方法可以在没有先验知识的情况下动态加载JS块?

时间:2018-11-06 22:39:46

标签: webpack

我正在尝试将Web应用程序模块化,以下是一些要点:

  • 有一个带有路由器的容器应用程序(主JS捆绑包,由网页加载)
  • 路由数据是动态获取的(或嵌入到HTML中)
  • 基于此数据动态构建路由
  • 每个路由均由动态提取的JS块呈现,应从路由数据中获取要提取的网址

所以我的问题是如何使用webpack做到这一点?我知道动态的import()功能,但是它需要一个我没有的模块路径,因为以后应该定义它,并且每个路由都应该有所不同。我正在考虑编写一个插件来覆盖webpack本身提供的块获取内部信息,但是也许有更好的方法吗?

1 个答案:

答案 0 :(得分:0)

我本人实际上已经经历过这种痛苦。 Webpack和Browserify 可以做到这一点,但我并不十分喜欢它的实现方式。您确实是正确的-您需要事先知道可以/需要加载什么。

我在当前设置中使用Go和GopherJS,但我上一个使用了NodeJS,SystemJSRollupJS,但直到今天我仍在使用SystemJS和RollupJS组件。

核心捆绑包

对于捆绑,您需要提前考虑,我个人捆绑了主要组件,通常将其称为core.bundle.js。在我的项目中,我将所有客户端内容放入其自己的jslib文件夹中:

Standard Project Layout

我使用TypeScript,所以在./src/index.ts中,我引用了我的主库..:

//@ts-ignore
import * as jquery from "jquery";
import * as popper from "popper.js"
import * as bootstrap from "../vendor/bootstrap/index.js"
import * as backbone from "backbone"
import * as lodash from "lodash"

//@ts-ignore
window.jquery = window.jQuery = window.$ = jquery;
//@ts-ignore
window.popper = popper;
//@ts-ignore
window.bootstrap = bootstrap;
//@ts-ignore
window.Backbone = backbone;
//@ts-ignore
window._ = window.lodash = lodash;

道歉,但GopherJS对于在某些地方需要某些东西可能会很有趣-但至少所有内容都是在需要放置在该窗口对象上的。

然后在同一文件中有动态加载器:

import * as SystemJS from 'systemjs';
import * as Promise from "bluebird";

export class DynamicModuleManager {

  constructor() {
    //load up the main package and initialise SystemJS for use

    SystemJS.config({
      baseURL: '/_pkg', //your dynamic requires will use this folder for the root
      meta: { '*': { scriptLoad: true } }
    });
  }

  public init() {
    //Register the modules you have already loaded!

    //@ts-ignore
    SystemJS.set('lodash', SystemJS.newModule({
      //@ts-ignore
      "default": window.lodash
    }));
    //@ts-ignore
    SystemJS.set('jquery', SystemJS.newModule({
      //@ts-ignore
      "default": window.$
    }));

    //Optionally add some shim/fake modules to allow NodeJS stuff to be used if needed

    //@ts-ignore
      SystemJS.set('child_process', SystemJS.newModule({
      ChildProcess: {}
    }));
  }

  public import(moduleId:string) {
    return SystemJS.import(moduleId).then((m) => {

      console.log(`Loading Module: ${moduleId}`);

      //m is the module object just like you had "required" it
      return m

    });
  }

}

如果您使用打字稿,那么我的tsconfig对于核心捆绑包是这样的:

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es5",
    "lib": ["es2015", "dom"],
    "baseUrl": "./src/",
    "outDir": "./build/",
    "inlineSourceMap": true,
    "paths": {
      "*": [
        "./node_modules/*"
      ]
    }
  },
  "exclude": [
    "node_modules"
  ],
  "include": [
    "src/**/*"
  ]
}

然后汇总配置如下:

// rollup.config.js
import builtins from 'rollup-plugin-node-builtins';
import nodeResolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import json from 'rollup-plugin-json';

export default {
  input: './build/index.js',
  output: {
    file: '../assets/js/core.bundle.js',
    format: 'cjs'
  },
  name: 'core_bundle',
  plugins: [
    json(),
    builtins(),
    nodeResolve(),
    commonjs(),
  ]
}

我不使用rollup-typescript插件,因为它导致了我的问题,所以它分三个阶段进行构建:

$ cd ~/your_web_project/jslib

$ npm init && npm install --save jquery... [OTHER DEPS] 
## When you clone just make sure to do npm install

$ tsc -p tsconfig.json 
## if you are running typescript remember to install globally

$ rollup -c
## again install globally

现在您将拥有〜/ your_web_project / assets / js / core.bundle.js

子模块

因此,现在您可以构建其他模块/块。对于上述配置,您需要将它们放在您喜欢的服务器提供的目录中,或者从节点内的路由中获取,例如:GET / _pkg /:module_name->〜/ your_web_project / resources / _pkg /:module_name.js < / p>

在您的模块上,您需要做的就是设置另一个jslib文件夹,大致相同,但是这次向rollupjs介绍您的外部软件包...

Dynamically Loadable VSCode/Monaco Environment for Golang Folder Structure

// rollup.config.js
import nodeResolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import postcss from 'rollup-plugin-postcss';
import json from 'rollup-plugin-json';
import alias from 'rollup-plugin-alias';
import babel from 'rollup-plugin-babel';
import * as filepath from "path"


//export an array of webpack configs for multiple modules..
export default [
  {
    input: './build/index.js',
    external : ["lodash", "jquery"], //make sure to make the module aware of anything external otherwise it will get bundled!
    output: {
      file: '../resources/devide.go.js',
      format: 'cjs',
    },
    plugins: [
      nodeResolve({
        module: true,
        jsnext: true,
        extensions: [ '.js', '.json' ]
      }),
      commonjs(),
      alias({
        'golangcli': filepath.resolve(__dirname, 'build/golangcli/client'),
        'vscode': require.resolve('monaco-languageclient/lib/vscode-compatibility')
      }),
      json(),
      postcss({
        plugins: [] //you can even embed CSS in your sources and have it load here...
      }),
      babel({
        "presets": ["@babel/preset-env"],
        "plugins": ["@babel/plugin-syntax-dynamic-import"]
      })
    ]
  }
];

上面的示例有点极端,因为在与Rollup捆绑在一起之前我使用Typescript和Babel。

tsconfig也需要进行一些细微更改:

{
  "compilerOptions": {
    "noImplicitAny": false,
    "module": "es2015",
    "target": "es2015",
    "lib": ["es2015", "dom"],
    "baseUrl": "./src/",
    "outDir": "./build/",
    "inlineSourceMap": false,
    "moduleResolution": "node",
    "skipLibCheck": true,
    "paths": {
      "*": [
        "./node_modules/*"
      ]
    }
  },
  "include": [
    "src/**/*"
  ]
}

这将在源开始汇总时强制进行适当的树抖动,并确保仅导入实际需要的内容。当Monaco有效负载超过10MB时,这一点很重要!

Rollup是一个很好的工具包,我什至可以使用它来汇总整个服务器端代码库,以便可以复制它们并运行依赖项,并且所有这些都没有问题(只要您拥有节点二进制文件),并且效果很好使用Golang,Buffalo和GopherJS设置。

TL; DR

以上问题是我长期努力解决的问题,我认为这是我有史以来的第一个答案。毫无疑问,上述解决方案中存在漏洞,但希望它能激发人们的灵感!