私はWebアプリを構築していて、Browserifyを知り、愛するようになっています。しかし、1つのことが私を悩ませました。
es6-promise
やobject-assign
(npmのパッケージ)などの古いブラウザーでシム/ポリフィルする必要があるいくつかのES6機能を使用しています。
現在、私はそれらを必要とする各モジュールにそれらをロードしています:
var assign = require('object-assign');
var Promise = require('es6-promise');
私はこれが絶対に行く方法ではないことを知っています。保守が難しく、requiresを介してES6機能に「依存」するのではなく、透過的にES6機能を使用したいと思います。
これらのようなシムをロードするための決定的な方法は何ですか?私はインターネットの周りでいくつかの例を見てきましたが、それらはすべて異なります。私はできた:
それらを外部にロードします。
var bundle = browserify();
bundle.require('s6-promise');
// or should I use it bundle.add to make sure the code is runned???
私がここで抱えている問題は、モジュールがブラウザにロードされる順序がわからないことです。したがって、ポリフィル機能を必要とするコールサイトでは、ポリフィルがまだ発生していない可能性があります。
これには、バックエンドコードがこれらのポリフィルの恩恵を受けることができないという追加の欠点があります(何かが欠けている場合を除く)。
browserify-shim
または同様のものを使用してください。これがES6機能でどのように機能するかはよくわかりません。
ポリフィリングを手動で設定します。
Object.assign = require('object-assign');
モジュールにポリフィルを必要としないでください。これはアンチパターンです。モジュールは、ランタイムにパッチが適用されていることを前提としている必要があり(必要な場合)、それはコントラクトの一部である必要があります。この良い例はReactJSで、ライブラリが機能できるようにランタイムの最小要件を明示的に定義しています。 http://facebook.github.io/react/docs/working-with-the-browser .html#browser-support-and-polyfills
ポリフィルサービス(例: https://cdn.polyfill.io/ )を使用して、ページの上部に最適化されたスクリプトタグを含め、ランタイムにピースが正しくパッチされるようにすることができます。最新のブラウザはペナルティを受けませんが、必要です。
または、 https://cdn.polyfill.io/v2/docs/ でポリフィルサービスを使用します
これが私が使っている方法です。重要なのは、メインエントリファイルの先頭でポリフィルをエクスポートする必要があるということです。
// Using ES6 imports
import './polyfill';
// Using CommonJS style
require('./polyfill');
... // rest of your code goes here
// Using ES6 export
export * from './polyfill';
// Using CommonJS style
var polyfill = require('./polyfill');
... // rest of your code goes here
後者の方法のいずれかを実行すると、ポリフィルが正しくロードされます。
以下に私のポリフィルの例を示します。
import './polyfill/Array.from';
import './polyfill/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;
};
}());
}
私のために働いた1つの解決策はbundle.add
を使用することでした
バンドルを2つの部分に分割しました。アプリコードの場合はapp.js
、ライブラリの場合はappLib.js
です(これは頻繁に変更されないため、キャッシュされます)。
https://github.com/sogko/gulp-recipes/tree/master/browserify-separating-app-and-vendor-bundles を参照してください
appLibs.js
の場合、スクリプトのロード時にロードする必要があるため、ポリフィルには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>
このように、他のライブラリが初期化される前であっても、すべてのポリフィルがロードされると想定するのは安全のようです。それが最良の選択肢かどうかはわかりませんが、私にとってはうまくいきました。
私の完全なセットアップ:
"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()));
}
ご覧のように