使用 Browserify 加载 polyfills 和 shims 的正确方法是什么

What is the correct way to load polyfills and shims with Browserify

我正在构建一个网络应用程序,我开始了解并喜欢上 Browserify。不过有一件事让我很困扰。

我正在使用一些 ES6 功能,这些功能需要在旧版浏览器中 shimmed/polyfilled,例如 es6-promiseobject-assign(npm 上的软件包)。

目前我只是将它们加载到需要它们的每个模块中:

var assign  = require('object-assign');
var Promise = require('es6-promise');

我知道这绝对不是要走的路。它很难维护,我想透明地使用 ES6 特性,而不是必须通过 requires "depend" 对它们进行

加载这些 shim 的最终方法是什么?我在互联网上看到过几个例子,但它们都不一样。我可以:

一个对我有用的解决方案是使用 bundle.add

我将我的包分成两部分,app.js 用于应用程序代码,appLib.js 用于库(这一部分将被缓存,因为它不经常更改)。

https://github.com/sogko/gulp-recipes/tree/master/browserify-separating-app-and-vendor-bundles

对于 appLibs.js 我对 polyfill 使用 bundle.add,因为它们必须在加载脚本时加载,而我对其他库使用 bundle.require,只有在app.js.

内需要
polyfills.forEach(function(polyfill) {
   b.add(polyfill);
});
libs.forEach(function(lib) {
  b.require(lib);
});

页面按顺序加载这 2 个包:

<head>
    ...
    <script type="text/javascript" src="appLibs.js" crossorigin></script>
    <script type="text/javascript" src="app.js" crossorigin></script>
    ...
</head>

这样似乎可以安全地假设所有的 polyfill 甚至在其他库初始化之前都会被加载。不确定这是最好的选择,但它对我有用。

我的完整设置:

"use strict";

var browserify   = require('browserify');
var gulp         = require('gulp');
var gutil        = require('gulp-util');
var handleErrors = require('../util/handleErrors');
var source       = require('vinyl-source-stream');
var watchify     = require("watchify");
var livereload   = require('gulp-livereload');
var gulpif       = require("gulp-if");
var buffer       = require('vinyl-buffer');
var uglify       = require('gulp-uglify');


// polyfills should be automatically loaded, even if they are never required
var polyfills = [
    "intl"
];

var libs = [
    "ajax-interceptor",
    "autolinker",
    "bounded-cache",
    "fuse.js",
    "highlight.js",
    "imagesloaded",
    "iscroll",
    "jquery",
    "keymaster",
    "lodash",
    "medium-editor",
    "mime-db",
    "mime-types",
    "moment",
    "packery",
    "q",
    "rangy",
    "spin.js",
    "steady",
    "store",
    "string",
    "uuid",
    "react-dnd"
];


// permits to create a special bundle for vendor libs
// See https://github.com/sogko/gulp-recipes/tree/master/browserify-separating-app-and-vendor-bundles
gulp.task('browserify-libs', function () {
    var b = browserify({
        debug: true
    });

    polyfills.forEach(function(polyfill) {
        b.add(polyfill);
    });
    libs.forEach(function(lib) {
        b.require(lib);
    });

    return b.bundle()
        .on('error', handleErrors)
        .pipe(source('appLibs.js'))
        // TODO use node_env instead of "global.buildNoWatch"
        .pipe(gulpif(global.buildNoWatch, buffer()))
        .pipe(gulpif(global.buildNoWatch, uglify()))
        .pipe(gulp.dest('./build'));
});



// Inspired by http://truongtx.me/2014/08/06/using-watchify-with-gulp-for-fast-browserify-build/

gulp.task('browserify',['cleanAppJs','browserify-libs'],function browserifyShare(){
    var b = browserify({
        cache: {},
        packageCache: {},
        fullPaths: true,
        extensions: ['.jsx'],
        paths: ['./node_modules','./src/'],
        debug: true
    });
    b.transform('reactify');

    libs.forEach(function(lib) {
        b.external(lib);
    });

    // TODO use node_env instead of "global.buildNoWatch"
    if ( !global.buildNoWatch ) {
        b = watchify(b);
        b.on('update', function() {
            gutil.log("Watchify detected change -> Rebuilding bundle");
            return bundleShare(b);
        });
    }
    b.on('error', handleErrors);

    //b.add('app.js'); // It seems to produce weird behaviors when both using "add" and "require"

    // expose does not seem to work well... see https://github.com/substack/node-browserify/issues/850
    b.require('app.js',{expose: 'app'});

    return bundleShare(b);
});



function bundleShare(b) {
    return b.bundle()
        .on('error', handleErrors)
        .pipe(source('app.js'))
        .pipe(gulp.dest('./build'))
        // TODO use node_env instead of "global.buildNoWatch"
        .pipe(gulpif(!global.buildNoWatch, livereload()));
}

如你所见

不需要在您的模块中使用 polyfill,这是一种反模式。您的模块应该假定运行时已打补丁(需要时),并且这应该是合同的一部分。 ReactJS 就是一个很好的例子,它们明确定义了运行时的最低要求,以便库可以工作:http://facebook.github.io/react/docs/working-with-the-browser.html#browser-support-and-polyfills

您可以使用 polyfill 服务(例如:https://cdn.polyfill.io/)在您的页面顶部包含一个优化的脚本标记,以确保使用您需要的部分正确修补运行时,而现代浏览器会不会受到惩罚。

或使用 polyfill 服务 https://cdn.polyfill.io/v2/docs/

这是我正在使用的方法。关键是您必须 导出您的 polyfill 在您的主入口文件的顶部正确。

以下将不起作用:

// Using ES6 imports
import './polyfill';

// Using CommonJS style
require('./polyfill');

... // rest of your code goes here

您实际上需要导出 polyfill:

// Using ES6 export
export * from './polyfill';

// Using CommonJS style
var polyfill = require('./polyfill');

... // rest of your code goes here

如果您执行后一种方法,您的 polyfill 将正确加载。

您可以在下面找到我的 polyfill 示例。

polyfill.js:

import './polyfill/Array.from';
import './polyfill/Object.assign';

Object.assign:

if (typeof Object.assign !== 'function') {
  (function iife() {
    const ObjectHasOwnProperty = Object.prototype.hasOwnProperty;

    /**
     * Copy the values of all enumerable own properties from one source
     * object to a target object. It will return the target object.
     * @param  {Object}  target  The target object.
     * @param  {Object}  source  The source object.
     * @return  {Object}  The target object.
     */
    function shallowAssign(target, source) {
      if (target === source) return target;
      Object.keys(source).forEach((key) => {
        // Avoid bugs when hasOwnProperty is shadowed
        if (ObjectHasOwnProperty.call(source, key)) {
          target[key] = source[key];
        }
      });
      return target;
    }

    /**
     * Copy the values of all enumerable own properties from one source
     * object to a target object. It will return the target object.
     * @param  {Object}  target  The target object.
     * @param  {Object}  source  The source object.
     * @return  {Object}  The target object.
     */
    Object.assign = function assign(target, ...sources) {
      if (target === null || target === undefined) {
        throw new TypeError('Cannot convert undefined or null to object');
      }
      sources.forEach((source) => {
        if (source !== null) { // Skip over if undefined or null
          shallowAssign(Object(target), Object(source));
        }
      });
      return target;
    };
  }());
}