Related
I am using Webpack 2 in my project to transpile and bundle ReactJS files along with a few other tasks. Here's what my config looks like:
var webpack = require("webpack");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
var config = {
module: {
loaders: [
{
exclude: /(node_modules)/,
loader: "babel-loader",
query: {
presets: ["es2015", "react"]
}
},
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: ["css-loader", "sass-loader"]
})
}
]
},
plugins: [
new webpack.optimize.UglifyJsPlugin({ mangle: false, sourcemap: false }),
new ExtractTextPlugin({
filename: "../../public/dist/main.css"
})
],
};
var indexConfig = Object.assign({}, config, {
name: "index",
entry: "./public/javascripts/build/index.js",
output: {
path: __dirname + "/public/dist",
filename: "index.min.js",
},
});
var aboutConfig = Object.assign({}, config, {
name: "about",
entry: "./public/javascripts/build/about.js",
output: {
path: __dirname + "/public/dist",
filename: "about.min.js"
},
});
// Return Array of Configurations
module.exports = [
indexConfig, aboutConfig
];
As evident, I am using multiple build configurations for JS, one for each page. Now, I need to add some Bootstrap JS to the mix for which I also need JQuery and Tether. Thus, I have the following 3 files in my library folder:
jquery-3.2.1.min.js
tether.min.js
bootstrap.min.js
I need these 3 files to be concatenated and bundled along with the main JS files being emitted by Webpack (e.g., index.min.js, etc.). To do so, I modified my entry item thus:
entry: {
a: "./public/javascripts/lib/jquery-3.2.1.min.js",
b: "./public/javascripts/lib/tether.min.js",
b: "./public/javascripts/lib/bootstrap.min.js",
c: "./public/javascripts/build/index.js"
}
However, doing so throws the following error:
ERROR in chunk a [entry]
index.min.js
Conflict: Multiple assets emit to the same filename index.min.js
ERROR in chunk b [entry]
index.min.js
Conflict: Multiple assets emit to the same filename index.min.js
ERROR in chunk c [entry]
index.min.js
Conflict: Multiple assets emit to the same filename index.min.js
Obviously this is because Webpack is expecting multiple output files for multiple entry items. Is there any way to overcome this problem? An existing question illustrating a similar problem doesn't seem to have any acceptable answer at the moment.
UPDATE:
Tried using the Commons chunk plugin as suggested by terales, but Webpack threw the following error this time:
ERROR in multi jquery tether bootstrap
Module not found: Error: Can't resolve 'jquery' in '/home/ubuntu/panda'
# multi jquery tether bootstrap
ERROR in multi jquery tether bootstrap
Module not found: Error: Can't resolve 'tether' in '/home/ubuntu/panda'
# multi jquery tether bootstrap
ERROR in multi jquery tether bootstrap
Module not found: Error: Can't resolve 'bootstrap' in '/home/ubuntu/panda'
# multi jquery tether bootstrap
ERROR in chunk vendor [entry]
index.min.js
Conflict: Multiple assets emit to the same filename index.min.js
Your libs shouldn't be entries. They called "vendors" in Webpack's terms.
See minimum working example repo.
In your code you should implicitly extract common vendor chunk:
var config = {
entry: { // <-- you could make two entries
index: './index.js', // in a more Webpack's way,
about: './about.js' // instead of providing array of confings
},
output: {
filename: '[name].min.js',
path: __dirname + '/dist'
},
module: {
loaders: [
// Fix error 'JQuery is not defined' if any
{ test: require.resolve("jquery"), loader: "expose-loader?$!expose-loader?jQuery" },
]
},
plugins: [
// this assumes your vendor imports exist in the node_modules directory
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module) {
return module.context && module.context.indexOf('node_modules') !== -1;
}
})
]
};
I'm having some trouble getting this WebComponents polyfill + native-shim to work right across all devices, though webpack.
Some background on my setup:
* Webpack2 + babel-6
* app is written in ES6, transpiling to ES5
* imports a node_module package written in ES6, which defines/registers a CustomElement used in the app
So the relevant webpack dev config looks something like this:
const config = webpackMerge(baseConfig, {
entry: [
'webpack/hot/only-dev-server',
'#webcomponents/custom-elements/src/native-shim',
'#webcomponents/custom-elements',
'<module that uses CustomElements>/dist/src/main',
'./src/client',
],
output: {
path: path.resolve(__dirname, './../dist/assets/'),
filename: 'app.js',
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
options: {
cacheDirectory: true,
},
include: [
path.join(NODE_MODULES_DIR, '<module that uses CustomElements>'),
path.join(__dirname, '../src'),
],
},
],
},
...
key take aways:
* I need CustomElement poly loaded before <module that uses CustomElements>
* I need <module that uses CustomElements> loaded before my app soure
* <module that uses CustomElements> is ES6 so we're transpiling it ( thus the include in the babel-loader).
The above works as-expected in modern ES6 browsers ( IE desktop Chrome ), HOWEVER
it does not work in older browsers. I get the following error in older browsers, for example iOS 8:
SyntaxError: Unexpected token ')'
pointing to the opening anonymous function in the native-shim pollyfill:
(() => {
'use strict';
// Do nothing if `customElements` does not exist.
if (!window.customElements) return;
const NativeHTMLElement = window.HTMLElement;
const nativeDefine = window.customElements.define;
const nativeGet = window.customElements.get;
So it seems to me like the native-shim would need to be transpiled to ES5:
include: [
+ path.join(NODE_MODULES_DIR, '#webcomponents/custom-elements/src/native-shim'),
path.join(NODE_MODULES_DIR, '<module that uses CustomElements>'),
path.join(__dirname, '../src'),
],
...but doing so now breaks both Chrome and iOS 8 with the following error:
app.js:1 Uncaught TypeError: Failed to construct 'HTMLElement': Please use the 'new' operator, this DOM object constructor cannot be called as a function.
at new StandInElement (native-shim.js:122)
at HTMLDocument.createElement (<anonymous>:1:1545)
at ReactDOMComponent.mountComponent (ReactDOMComponent.js:504)
at Object.mountComponent (ReactReconciler.js:46)
at ReactCompositeComponentWrapper.performInitialMount (ReactCompositeComponent.js:371)
at ReactCompositeComponentWrapper.mountComponent (ReactCompositeComponent.js:258)
at Object.mountComponent (ReactReconciler.js:46)
at Object.updateChildren (ReactChildReconciler.js:121)
at ReactDOMComponent._reconcilerUpdateChildren (ReactMultiChild.js:208)
at ReactDOMComponent._updateChildren (ReactMultiChild.js:312)
.. which takes me to this constructor() line in the native-shim:
window.customElements.define = (tagname, elementClass) => {
const elementProto = elementClass.prototype;
const StandInElement = class extends NativeHTMLElement {
constructor() {
Phew. So it's very unclear to me how we actually include this in a webpack based build, where the dependency using CustomElements is ES6 ( and needs transpiling).
Transpiling the native-shim to es5 doesn't work
using the native-shim as-is at the top of the bundle entry point doesn't work for iOS 8, but does for Chrome
not including the native-shim breaks both Chrome and iOS
I'm really quite frustrated with web components at this point. I just want to use this one dependency that happens to be built with web components. How can I get it to work properly in a webpack build, and work across all devices? Am I missing something obvious here?
My .babelrc config for posterity sake (dev config most relevant):
{
"presets": [
["es2015", { "modules": false }],
"react"
],
"plugins": [
"transform-custom-element-classes",
"transform-object-rest-spread",
"transform-object-assign",
"transform-exponentiation-operator"
],
"env": {
"test": {
"plugins": [
[ "babel-plugin-webpack-alias", { "config": "./cfg/test.js" } ]
]
},
"dev": {
"plugins": [
"react-hot-loader/babel",
[ "babel-plugin-webpack-alias", { "config": "./cfg/dev.js" } ]
]
},
"dist": {
"plugins": [
[ "babel-plugin-webpack-alias", { "config": "./cfg/dist.js" } ],
"transform-react-constant-elements",
"transform-react-remove-prop-types",
"minify-dead-code-elimination",
"minify-constant-folding"
]
},
"production": {
"plugins": [
[ "babel-plugin-webpack-alias", { "config": "./cfg/server.js" } ],
"transform-react-constant-elements",
"transform-react-remove-prop-types",
"minify-dead-code-elimination",
"minify-constant-folding"
]
}
}
}
I was able to achieve something similar with the .babelrc plugin pipeline below. It looks like the only differences are https://babeljs.io/docs/plugins/transform-es2015-classes/ and https://babeljs.io/docs/plugins/transform-es2015-classes/, but I honestly can't remember what problems those were solving specifically:
{
"plugins": [
"transform-runtime",
["babel-plugin-transform-builtin-extend", {
"globals": ["Error", "Array"]
}],
"syntax-async-functions",
"transform-async-to-generator",
"transform-custom-element-classes",
"transform-es2015-classes"
]
}
My .travis.yml
language: node_js
node_js:
- "0.12"
before_install:
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
I have only added a few very simple tests so far (checking that class attributes exist).
I can see the tests are executed.
Then, the last few lines in the Travis output are this:
WARN [web-server]: 404: /css/style.min.css?1435068425.642
No output has been received in the last 10 minutes, this potentially indicates a stalled build or something wrong with the build itself.
The build has been terminated
If the tests are running, then the build and dependencies must have been installed already?
So why is the process not finishing once all tests are executed?
karma.config:
module.exports = function(config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: [
'jasmine',
'requirejs'
],
// list of files / patterns to load in the browser
files: [
{pattern: 'js/vendor/**/*.js', included: false},
{pattern: 'js/*.js', watched: true, included: false},
{pattern: 'test/**/*Spec.js', watched: true, included: false},
{pattern: 'css/**/*.css', included: false},
'test/test-main.js'
],
// list of files to exclude
exclude: [
'test/lib/**/*.js',
'js/vendor/**/test/*.js', //do not include the vendor tests
'js/_admin.js'
],
preprocessors: {
},
reporters: ['progress'],
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Firefox'], //'Chrome', 'PhantomJS', 'PhantomJS_custom'
singleRun: false,
});//config.set
};//module.exports
test-main.js in folder test:
var allTestFiles = [];
var TEST_REGEXP = /(spec|test)\.js$/i;
Object.keys(window.__karma__.files).forEach(function(file) {
if (TEST_REGEXP.test(file)) {
// Normalize paths to RequireJS module names.
allTestFiles.push(file);
}
//console.log('test files:', allTestFiles);
});
require.config({
baseUrl: '/base',
paths: {
'jquery': './js/vendor/jquery/jquery-2.1.4.min',
'jquery-ui': './js/vendor/jquery-ui-1.11.4.custom/jquery-ui.min',
'underscore': './js/vendor/underscore/underscore-min',
'backbone': './js/vendor/backbone/backbone-min',
'mustache': './js/vendor/mustache/mustache.min',
'domReady': './js/vendor/requirejs/plugins/domReady/domReady',
'text': './js/vendor/requirejs/plugins/text/text',
//------------------------------------
//custom requireJS application modules
'my': './js/my',
'my-CSS': './js/my-CSS'
},
shim: {
'underscore': {
exports: '_'
}
},
deps: allTestFiles,
callback: window.__karma__.start
});
The my-CSS module loads the css like this:
//custom requireJS module to hold the crowdUI class
define(["my"], function (my) { //prerequisites
'use strict';
//load the CSS definitions
document.head.appendChild(
my.createElement('link', {
attribute: {
id: 'my-CSS',
href: './css/style.min.css?' + crowdUI.TIMESTAMP,
rel: 'stylesheet',
type: 'text/css'
}
})
);
});
If the issue is just that your task requires more than 10 minutes during which it produces no output, the fix is simple: prepend your command with travis_wait.
For instance travis_wait myCommand instead of just myCommand.
I have my bower.json like:
..
"install": {
"base": "static/my-project/libs",
"path": {
..
"eot": "{name}/fonts",
"otf": "{name}/fonts",
"svg": "{name}/fonts",
"woff": "{name/fonts",
..
}
},
..
to install font related resources to static/my-project/libs/bower-package-name/fonts/.
But I found that slick.js expects fonts to be placed in bower-package-name/css/fonts, not bower-package-name/fonts/.
Since I have other dependency that expects fonts to be located in bower-package-name/fonts/, I want to install these font related resources to multiple paths, both in bower-package-name/fonts/ and bower-package-name/css/fonts.
Is there any way to install bower dependencies to multiple paths like:
..
"install": {
"base": "static/my-project/libs",
"path": {
..
"eot": ["{name}/fonts", "{name}/css/fonts"],
"otf": ["{name}/fonts", "{name}/css/fonts"],
"svg": ["{name}/fonts", "{name}/css/fonts"],
"woff": ["{name}/fonts", "{name}/css/fonts"],
..
}
},
..
for example?
If you are using grunt, you can use grunt-contrib-copy to copy your resources to different location.
Sample grunt copy task would look like (from the above source):
copy: {
main: {
files: [
// includes files within path
{expand: true, src: ['path/*'], dest: 'dest/', filter: 'isFile'},
// includes files within path and its sub-directories
{expand: true, src: ['path/**'], dest: 'dest/'},
// makes all src relative to cwd
{expand: true, cwd: 'path/', src: ['**'], dest: 'dest/'},
// flattens results to a single level
{expand: true, flatten: true, src: ['path/**'], dest: 'dest/', filter: 'isFile'},
],
},
},
I have Browserify, 6to5ify and Karma to play nice, successfully running my specs. When I add code coverage however, things go south. I've tried several approaches:
Add browserify-istanbul transform to my karma.conf.js. However, this results in it trying to run instrumentation on my spec-files as well it would appear.
Run coverage preprocessor on my source files. But because istanbul (even douglasduteil/karma-coverage#next) doesn't read my 6to5ify browserify transform, this crashes immediately on the first file it tries to parse (because of the import statement), or when I use karma-coverage#next, it doesn't respect the browser mapping in my package.json (mobile project, mapped Backbone to Exoskeleton).
Right now my karma.conf.js looks like this:
module.exports = function(karma){
karma.set({
frameworks: ["browserify", "mocha", "chai-sinon"],
browserify: {
debug: true,
extensions: [".js", ".hbs"],
transform: ["6to5ify", "hbsfy"]
},
reporters: ["dots", "osx", "junit", "coverage"],
coverageReporter: {
type: "text"
},
junitReporter: {
outputFile: "spec/reports/test-results.xml"
},
preprocessors: {
"src/javascript/**/*": ["coverage"],
"spec/**/*": ["browserify"]
},
browsers: ["PhantomJS"],
files: ["spec/unit/**/*Spec.js"],
logLevel: "LOG_DEBUG",
autoWatch: true
});
};
I'm kind of lost how to get this all working together. I tried following these instructions, but that didn't work because it didn't follow my browser node in package.json. Any help would be greatly appreciated.
So, apparently I need browserify-istanbul, and I need the browserify configure hook, like so:
var to5ify = require('6to5ify');
var hbsfy = require('hbsfy');
var cover = require('browserify-istanbul');
var coverOptions = {
ignore: ['**/*Spec.js', '**/lib/*.js', '**/fixtures/*.hbs'],
defaultIgnore: true
}
module.exports = function(karma){
karma.set({
frameworks: ["browserify", "mocha", "chai-sinon"],
browserify: {
debug: false,
extensions: [".js", ".hbs"],
configure: function(bundle){
bundle.on('prebundle', function(){
bundle
.transform(to5ify)
.transform(hbsfy)
.transform(cover(coverOptions));
});
}
},
reporters: ["dots", "osx", "junit", "coverage"],
coverageReporter: {
type: "text"
},
junitReporter: {
outputFile: "spec/reports/test-results.xml"
},
preprocessors: {
"spec/**/*": ["browserify"]
},
browsers: ["PhantomJS"],
files: ["spec/unit/**/*Spec.js"],
logLevel: "LOG_DEBUG",
autoWatch: true
});
};