使用Browserify加载polyfill和垫片的正确方法是什么

时间:2015-01-09 23:30:29

标签: node.js npm browserify

我正在构建一个网络应用程序,我开始了解并喜欢Browserify。但有一件事让我感到烦恼。

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

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

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

我知道这绝对不是要走的路。它很难维护,我想透明地使用ES6功能,而不是通过需要“依赖”它们。

加载这些垫片的最终方法是什么?我在互联网上看到了几个例子,但它们各不相同。我可以:

  • 从外部加载它们:

    var bundle = browserify(); 
    bundle.require('s6-promise');
    // or should I use it bundle.add to make sure the code is runned???
    

    我在这里遇到的问题是我不知道模块的顺序 将在浏览器中加载。因此,填充可能不会发生 但是在需要polyfilled功能的呼叫站点。

    这还有一个额外的缺点,即后端代码无法从中受益 polyfills(除非我遗漏了什么)。

  • 使用browserify-shim或类似的东西。我真的不明白这对ES6功能有什么作用。

  • 手动设置polyfilling:

    Object.assign = require('object-assign');
    

4 个答案:

答案 0 :(得分:12)

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

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

答案 1 :(得分:1)

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

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

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

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

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

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

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

这样可以安全地假设即使在初始化其他lib之前也会加载所有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()));
}

如你所见

答案 2 :(得分:1)

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

答案 3 :(得分:1)

这是我正在使用的方法。关键是您必须在主条目文件的顶部正确导出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

如果您使用后一种方法中的任何一种,您的polyfills将正确加载。

下面你可以找到我的polyfills的例子。

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;
    };
  }());
}