使用 Browserify 加载 polyfills 和 shims 的正确方法是什么
What is the correct way to load polyfills and shims with Browserify
我正在构建一个网络应用程序,我开始了解并喜欢上 Browserify。不过有一件事让我很困扰。
我正在使用一些 ES6 功能,这些功能需要在旧版浏览器中 shimmed/polyfilled,例如 es6-promise
和 object-assign
(npm 上的软件包)。
目前我只是将它们加载到需要它们的每个模块中:
var assign = require('object-assign');
var Promise = require('es6-promise');
我知道这绝对不是要走的路。它很难维护,我想透明地使用 ES6 特性,而不是必须通过 requires "depend" 对它们进行
加载这些 shim 的最终方法是什么?我在互联网上看到过几个例子,但它们都不一样。我可以:
从外部加载它们:
var bundle = browserify();
bundle.require('s6-promise');
// or should I use it bundle.add to make sure the code is runned???
我这里的问题是我不知道模块的顺序
将被加载到浏览器中。所以 polyfilling 可能没有发生
但在需要 polyfilled 功能的调用站点。
这还有一个额外的缺点,即后端代码无法从这些中受益
polyfills(除非我遗漏了什么)。
使用 browserify-shim
或类似的东西。我真的不明白这对 ES6 功能有何影响。
手动设置 polyfilling:
Object.assign = require('object-assign');
一个对我有用的解决方案是使用 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;
};
}());
}
我正在构建一个网络应用程序,我开始了解并喜欢上 Browserify。不过有一件事让我很困扰。
我正在使用一些 ES6 功能,这些功能需要在旧版浏览器中 shimmed/polyfilled,例如 es6-promise
和 object-assign
(npm 上的软件包)。
目前我只是将它们加载到需要它们的每个模块中:
var assign = require('object-assign');
var Promise = require('es6-promise');
我知道这绝对不是要走的路。它很难维护,我想透明地使用 ES6 特性,而不是必须通过 requires "depend" 对它们进行
加载这些 shim 的最终方法是什么?我在互联网上看到过几个例子,但它们都不一样。我可以:
从外部加载它们:
var bundle = browserify(); bundle.require('s6-promise'); // or should I use it bundle.add to make sure the code is runned???
我这里的问题是我不知道模块的顺序 将被加载到浏览器中。所以 polyfilling 可能没有发生 但在需要 polyfilled 功能的调用站点。
这还有一个额外的缺点,即后端代码无法从这些中受益 polyfills(除非我遗漏了什么)。
使用
browserify-shim
或类似的东西。我真的不明白这对 ES6 功能有何影响。手动设置 polyfilling:
Object.assign = require('object-assign');
一个对我有用的解决方案是使用 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;
};
}());
}