I'm following Maxime Fabre's tutorial on Webpack and am trying to get a really simple webpack bundle with 1 entry point and 2 chunks to work. Since both chunks require jquery and mustache, I'm using CommonsChunkPlugin to move the common dependencies up to the main bundle file, just like in the tutorial. I'm also using extract-text-webpack-plugin to extract styles from the chunks and put them in a separate CSS file.
My webpack.config.js:
var ExtractPlugin = require("extract-text-webpack-plugin");
var plugins = [
new ExtractPlugin("bundle.css"),
new webpack.optimize.CommonsChunkPlugin({
name: "vendors", //move dependencies to our main bundle file
children: true, //look for dependencies in all children
minChunks: 2 //how many times a dependency must come up before being extracted
})
];
module.exports = {
/*...*/
entry: "./src/index.js",
output: {
/*...*/
},
plugins: plugins,
module: {
loaders: [
/*...*/
{
test: /\.scss$/,
loader: ExtractPlugin.extract("style", "css!sass")
//loaders: ["style", "css", "sass"]
},
/*...*/
]
}
};
Relevant code in the entry point (I'm using ES6 syntax and babel):
import "./styles.scss";
/*if something is in the page*/
require.ensure([], () => {
new (require("./Components/Chunk1").default)().render();
});
/*if something else is in the page*/
require.ensure([], () => {
new (require("./Components/Chunk2").default)().render();
});
Both chunk1 and chunk2 look something like this:
import $ from "jquery";
import Mustache from "mustache";
/*import chunk templates and scss*/
export default class /*Chunk1or2*/ {
render() {
$(/*some stuff*/).html(Mustache.render(/*some other stuff*/));
}
}
index.html:
<html>
<head>
<link rel="stylesheet href="build/bundle.css">
</head>
<body>
<script src="/build/main.js"></script>
</body>
</html>
When I run webpack the bundle builds just fine. However, in the browser I get a Uncaught TypeError: Cannot read property 'call' of undefined, and on closer inspection it looks like several modules end up as undefined in the final bundle.
My bug looks a lot like https://github.com/wenbing/webpack-extract-text-commons-chunk-bug. When I disable either extract-text-webpack-plugin or CommonsChunkPlugin and build it the webpack bundle works beautifully.
However even though I'm following a simple tutorial with 2 very common plugins the bug seems rare, so I'm assuming I'm messing up somewhere. What gives?
Related
I have managed to do that for vue by using Webpack config externals
First I included the CDN for Vue in my html file
index.html
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.12"></script>
Then I modified my webpack config
vue.config.js
module.exports = {
configureWebpack: {
// ...
externals: {
vue: 'Vue',
}
// ...
},
}
Things worked perfectly.
But when I tried with vue-class-component and vue-property-decorator, it didn't not worked as expected
index.html
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.12"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-class-component#7.2.5"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-property-decorator#9.0.0"></script>
vue.config.js
module.exports = {
configureWebpack: {
// ...
externals: {
vue: 'Vue',
'vue-class-component': 'VueClassComponent',
'vue-property-decorator': 'VuePropertyDecorator',
}
// ...
},
}
I noticed that the names of these files are different, end with .common.js and .umd.js
vue-class-component.common.js
vue-property-decorator.umd.js
Then I tried
vue.config.js
module.exports = {
configureWebpack: {
// ...
externals: {
vue: 'Vue',
'vue-class-component': 'commonjs2 vue-class-component',
'vue-property-decorator': 'umd vue-property-decorator',
}
// ...
},
}
But it did not work as well
Below are how I import these in my src/. Scripts are written in typescript
import Vue from 'vue'
// ...
import { Component } from 'vue-property-decorator'
Anyone knows how to handle externals in webpack with .common.js and .umd.js? Many thanks!
I don't think problem is necessarily in Webpack config...
If I try to load https://cdn.jsdelivr.net/npm/vue-class-component#7.2.5 it gives me Original file: /npm/vue-class-component#7.2.5/dist/vue-class-component.common.js which is CommonJS build - browser will not handle that. Try use link to a "browser compatible" build like https://cdn.jsdelivr.net/npm/vue-class-component#7.2.6/dist/vue-class-component.min.js
vue-property-decorator should be fine as UMD module should work in the browser...
BTW whats the point of all this? Why not let Webpack do its thing ? Its always better do download one big JS file then multiple smaller...
I am losing my mind with javascript in Foundation 6.4. I have no idea what's going on with this Webpack thing. It seems like some libraries/plugins work and some do not. My latest issue is with plyr (https://plyr.io/). I do not understand why TweenMax works 100% fine and plyr.js does not. What am I doing wrong?
This is the error I get..
app.js:23026 Uncaught ReferenceError: plyr is not defined
This is what my app.js looks like..
import $ from 'jquery';
import whatInput from 'what-input';
window.$ = $;
window.jQuery = $;
require('./TweenMax.js');
require('./plyr.js');
//import Foundation from 'foundation-sites';
// If you want to pick and choose which modules to include, comment out the above and uncomment
// the line below
import './lib/foundation-explicit-pieces';
$(document).foundation().ready(function(){
TweenMax.set(".logo-center", {transformOrigin:"50% 50%"});
var blast = plyr.setup('#blast', {
hideControls: true,
clickToPlay: false,
controls: []
});
});
I also have the path to plyr.js in my config.yml file:
# Your project's server will run on localhost:xxxx at this port
PORT: 8000
# Autoprefixer will make sure your CSS works with these browsers
COMPATIBILITY:
- "last 2 versions"
- "ie >= 10"
- "ios >= 9"
# UnCSS will use these settings
UNCSS_OPTIONS:
html:
- "src/**/*.html"
ignore:
- !!js/regexp .foundation-mq
- !!js/regexp ^\.is-.*
# Gulp will reference these paths when it copies files
PATHS:
# Path to dist folder
dist: "dist"
# Paths to static assets that aren't images, CSS, or JavaScript
assets:
- "src/assets/**/*"
- "!src/assets/{img,js,scss}/**/*"
# Paths to Sass libraries, which can then be loaded with #import
sass:
- "node_modules/foundation-sites/scss"
- "node_modules/motion-ui/src"
# Paths to JavaScript entry points for webpack to bundle modules
entries:
- "src/assets/js/app.js"
- "src/assets/js/plyr.js"
- "src/assets/js/TweenMax.js"
I assume you started your Foundation project from ZURB template. It uses a Gulpfile (gulpfile.babel.js) for JavaScript module bundling with Webpack.
Inside this script there is a Webpack configuration like below:
let webpackConfig = {
module: {
rules: [
{
test: /.js$/,
use: [
{
loader: 'babel-loader'
}
]
}
]
}
}
Webpack config can define, in the module section, some rules for modules (configure loaders, parser options, etc.).
In particular loaders enable webpack to process files and bundle them (or do other Webpack tasks).
So the specific configuration in gulpfile.babel.js tells to Webpack to use babel-loader for all files in /src folder with js extension.
However as explained in Webpack - Shimming some modules/libraries may expect global dependencies (e.g. $ for jQuery) or create globals which need to be exported.
To support these libraries, like Plyr, you can use expose-loader.
So add a module rule to Webpack config inside
gulpfile.babel.js, pointing to expose-loader for plyr.js:
let webpackConfig = {
module: {
rules: [
{
test: /plyr.js/,
use: [
{
loader: 'expose-loader',
options: 'plyr'
}
]
},
{
test: /.js$/,
use: [
{
loader: 'babel-loader'
}
]
}
]
}
}
No other changes are needed to your project files (config.yml, app.js).
This way you should access plyr as global variable like in your app.js:
var blast = plyr.setup('#blast', {
hideControls: true,
clickToPlay: false,
controls: []
});
You might need to use something like expose-loader so that plyr is available globally on window. Here's more info on how we used expose-loader in our project.
Without the actual project code, it is rather difficult to figure out what went wrong. But here's my take on the situation.
First off, why are you import plyr from a local file? Shouldn't it be require('plyr') instead of require('./plyr.js').
Also, why are you using plyr with a lower-case 'p' when the module exports the library as Plyr, with an upper-case 'P' in global binding, i.e., binding on window.
Your code:
plyr.setup('#blast', {
...
});
The plyr module:
! function (e, t) {
"object" == typeof exports && "undefined" != typeof module
? module.exports = t() // commonjs
: "function" == typeof define && define.amd
? define("Plyr", t) // requirejs
: e.Plyr = t() // window/global binding
}(this, function () {
...
});
Full dump here and here. Source here.
You can make it work by loading plyr.js in your index.html before loading your code and use the globally bound Plyr.
// index.html
<html>
<head>
<script src="plyr.js"></script> // window.Plyr = ...
</head>
<body>
...
<script src="bundle.js"></script>
</body>
</html>
//main.js
...
Plyr.setup('#blast', {
hideControls: true,
clickToPlay: false,
controls: []
});
You could also import plyr module in your code as a named import:
import Plyr from 'plyr';
...
Plyr.setup('#blast', {
hideControls: true,
clickToPlay: false,
controls: []
});
If all this still don't work, I believe it'd best if you share your project code - at least something more than you did alreday.
I am trying to load and bundle toastr as a dependency using webpack.
here is the entire webpack config file
var path = require('path');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var webpack = require('webpack');
const DEVELOPMENT = process.env.NODE_ENV === 'development';
const PRODUCTION = process.env.NODE_ENV === 'production';
module.exports = {
entry: {
main: './wwwroot/js/mainEntry.js',
vendor: ['jquery', 'tether',
'bootstrap', 'jquery-colorbox',
'jquery-confirm', 'jquery-validation',
'jquery-validation-unobtrusive',
'toastr', 'jquery.nicescroll',]
},
output: {
filename: '/js/[name].js',
path: path.resolve(__dirname, 'wwwroot'),
},
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
},
{
test: /\.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
loader: 'file-loader?name=[name].[ext]&publicPath=/fonts/&outputPath=/fonts/'
},
{
test: /\.(png|jpe?g|gif|ico)$/,
loader: 'file-loader?name=[name].[ext]&publicPath=/images/&outputPath=/images/'
}
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
// (the commons chunk name)
filename: "/js/vendor.js",
// (the filename of the commons chunk)
minChunks: 2,
}),
new ExtractTextPlugin({
filename: 'css/[name].min.css'
}),
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery"
})
],
};
and my entry js file as
//JS
import 'jquery-colorbox';
import 'slick-carousel';
import toastr from 'toastr';
//CSS
import './../../node_modules/bootstrap/dist/css/bootstrap.css';
import './../../node_modules/slick-carousel/slick/slick.css';
import './../../node_modules/jquery-colorbox/colorbox.css';
import './../../node_modules/toastr/build/toastr.css';
import './../../node_modules/jquery-confirm/css/jquery-confirm.css';
import './../../node_modules/font-awesome/css/font-awesome.css';
import './../../Modules/Components/Menu/menu.css';
import './../../wwwroot/css/lahuritv.css';
The bundle is created without any errors. And I can see that the toastr script is included in the bundle when I look at the output bundle. But the problem is that the variable toastr is not available in the browser window.
I tried looking for similar issue but couldn't find any. This is my first time trying to learn webpack. Any help is appreciated.
Simple solution :
//app.js
import toastr from 'toastr'
window.toastr = toastr
Webpack does not expose the modules you import, it bundles your code together with the needed dependencies, but they are still modular and have their own scope. Webpack does not just blindly combine the sources of all the files and certainly does not make everything global. If the entry file you posted is the whole file, then you're not doing anything at all.
Instead with webpack you would do the work in the JavaScript files you bundle. So to speak, all you include in your HTML are the bundles created by webpack, you don't include other scripts that try to access something in the bundle, but you rather do it directly in your bundle.
Of course you could expose it to window by explicitly defining it: window.toastr = toastr after importing toastr, but polluting the global scope is generally not a good idea and it's no different from just including toastr in a <script> tag. If the bundle is just supposed to be used as a library you could have a look at Authoring Libraries. But I think you're just doing a regular web app and you should get all the code together to be bundled by webpack and not rely on it in another <script> tag.
You should go through the Getting Started guide of the official docs. The example is very tiny (creates one DOM element) but shows you the concept of webpack apps and also uses an external dependency.
Question
When exporting a bundle in Webpack, how can I exclude 3rd-party module's peerDependency? (not 3rd-party module itself)
Background
I would like to create an UIkit with customized components on top of angular-material framework. With Webpack, I can bundle my customzied components and angular-material together into something like uikit.js, and then port to other application later on. However, I don't want to include angular module itself into this uikit.js.
Issue
It seems that Webpack is "clever" enough to notice that angular module is a dependency of angular-material module, and thus would export both angular module and angular-material module to the bundle. One can either use config.externals: {'angular': 'angular'} or new webpack.IgnorePlugin(/angular$/) to exclude angular module which are explicitly require in the app, but for peerDependency (i.e. the one require inside angular-material), it would still include it.
So, how could I exclude this 3rd-party depended modules out from export?
Example:
// app.js
var angular = require('angular');
var material = require('angular-material');
// ... other application logic
// webpack.config.js
var webpack = require('webpack');
module.exports = {
entry: {
app: './app.js'
},
module: {
loaders: [
// some module loaders
]
},
// This only excludes the angular module require by the app, not the one require by the angular-material
externals: {'angular': 'angular'},
plugins: [
// This is the same as externals, only the one required by app.js would be excluded
new webpack.IgnorePlugin(/angular$/)
]
};
In the webpack (version 4) config, I export vendor apps into a vendor bundle and chunk all like this:
entry: {
app: './app.js',
vendor: [ 'angular', 'angular-material', '...' ],
},
optimization: {
splitChunks: {
chunks: 'all',
}
},
Basically, this indicates which chunks will be selected for optimization and all means that chunks can be shared even between async and non-async chunks. Furthermore, How your modules are built and how you handle dependancies can further streamline your chunk size.
In addition you can provide a function whose return value will indicate whether to include each chunk:
module.exports = {
//...
optimization: {
splitChunks: {
chunks (chunk) {
// exclude `my-excluded-chunk`
return chunk.name !== 'my-excluded-chunk';
}
}
}
};
Here is a link to webpack explaining the splitChunks plugin.
I'm trying to get tinymce recognized by webpack. It sets a property named tinymce on window, so evidently one option is to require() it using syntax like this (described at the bottom of the EXPORTING section of the webpack docs):
require("imports?window=>{}!exports?window.XModule!./file.js
But in this example, how is ./file.js resolved? I installed tinymce via npm, and I can't figure out how to specify the right path to the tinymce.js file.
Regardless, I'd rather handle this in my configuration and be able to just require('tinymce') if possible, so I've installed exports-loader and added the following to my configuration (based on this discussion):
module: {
loaders: [
{
test: /[\/]tinymce\.js$/,
loader: 'exports?tinymce'
}
]
}
Unfortunately this isn't working. What's wrong with my configuration?
The tinymce module on npm can't be required directly, but contains 4 different distributions of the library. Namely:
tinymce/tinymce.js
tinymce/tinymce.min.js
tinymce/tinymce.jquery.js
tinymce/tinymce.jquery.min.js
To be able to do require('tinymce') in your code, you can add an alias in your webpack config, as well as a custom loader for your distribution of choice.
resolve: {
alias: {
// require('tinymce') will do require('tinymce/tinymce')
tinymce: 'tinymce/tinymce',
},
},
module: {
loaders: [
{
// Only apply on tinymce/tinymce
include: require.resolve('tinymce/tinymce'),
// Export window.tinymce
loader: 'exports?window.tinymce',
},
],
},
Where you can replace tinymce/tinymce with your distribution of choice.
Just like #cchamberlain I ended up using script loader for tinymce, but to load the plugins and other resources that were not required by default I used CopyWebpackPlugin instead of ES6 for more configurable solution.
var copyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
//...
plugins: [
new copyWebpackPlugin([
{ from: './node_modules/tinymce/plugins', to: './plugins' },
{ from: './node_modules/tinymce/themes', to: './themes' },
{ from: './node_modules/tinymce/skins', to: './skins' }
])
]
};
I was able to integrate tinyMCE in my Angular 2/TypeScript based project by using the imports-loader and exports-loader and the copy-webpack-plugin.
First ensure that the necessary dependencies are available and part of the packages.json file of your project:
npm i tinymce --save
npm i exports-loader --save-dev
npm i imports-loader --save-dev
npm i copy-webpack-plugin --save-dev
Then add the required loader to the loaders-section of your webpack configuration:
loaders: [
{
test: require.resolve('tinymce/tinymce'),
loaders: [
'imports?this=>window',
'exports?window.tinymce'
]
},
{
test: /tinymce\/(themes|plugins)\//,
loaders: [
'imports?this=>window'
]
}]
To make the copyWebpackPlugin available in your webpack configuration, import it in the header part of the webpack configuration file:
var copyWebpackPlugin = require('copy-webpack-plugin');
And, as Petri Ryhänen commented, add the following entry to the plugins-section of your webpack configuration:
plugins: [
new copyWebpackPlugin([
{ from: './node_modules/tinymce/plugins', to: './plugins' },
{ from: './node_modules/tinymce/themes', to: './themes' },
{ from: './node_modules/tinymce/skins', to: './skins' }
])
]
This step ensures that (required) addons of tinyMCE are also available in your webpack.
Finally to import tinyMCE in your Angular 2 component file, add
require('tinymce')
declare var tinymce: any;
to the import section and tinyMCE is ready to use.
I got this to work similar to how I bundle React to ensure I don't get two separate instances in DOM. I had some issues with imports / exports / expose loaders so instead I used script-loader.
In my setup I have a commons chunk that I use strictly for vendors (React / tinymce).
entry: { 'loading': '../src/app/entry/loading'
, 'app': '../src/app/entry/app'
, 'timeout': '../src/app/entry/timeout'
, 'commons': [ 'expose?React!react'
, 'expose?ReactDOM!react-dom'
, 'script!tinymce/tinymce.min.js'
]
}
This is working for me the same way that including the script from CDN would work however I now had errors because it could not find my themes / plugins / skins paths from my node_modules location. It was looking for them at paths /assets/plugins, /assets/themes, /assets/skins (I use webpack public path /assets/).
I resolved the second issue by mapping express to serve these two routes statically like so (es6):
const NODE_MODULES_ROOT = path.resolve(__dirname, 'node_modules')
const TINYMCE_PLUGINS_ROOT = path.join(NODE_MODULES_ROOT, 'tinymce/plugins')
const TINYMCE_THEMES_ROOT = path.join(NODE_MODULES_ROOT, 'tinymce/themes')
const TINYMCE_SKINS_ROOT = path.join(NODE_MODULES_ROOT, 'tinymce/skins')
router.use('/assets/plugins', express.static(TINYMCE_PLUGINS_ROOT))
router.use('/assets/themes', express.static(TINYMCE_THEMES_ROOT))
router.use('/assets/skins', express.static(TINYMCE_SKINS_ROOT))
After doing this window.tinymce / window.tinyMCE are both defined and functions same as CDN.
As an addition to this answer (thanks to Petri Ryhänen), I want to add my copyWebpackPlugin and tinymce.init() configuration adjustments.
new copyWebpackPlugin([{
context: './node_modules/tinymce/skins/lightgray',
from: './**/*',
to: './tinymce/skin',
}]),
With this configuration you will get all skin files in {output}/tinymce/skin folder.
Then you can initialize tinymce like this:
import tinymce from 'tinymce/tinymce';
// A theme is also required
import 'tinymce/themes/modern/theme'; // you may change to 'inlite' theme
// Any plugins you want to use has to be imported
import 'tinymce/plugins/advlist/plugin';
// ... possibly other plugins
// Then anywhere in this file you can:
tinymce.init({
// ... possibly other options
skin_url: '/tinymce/skin', // <-- !!! here we tell tinymce where
// to load skin files from
// ... possibly other options
});
With this I have both development and production builds working normally.
We use TinyMCE jQuery 4.1.6 and the accepted answer did not work for us because window seems to be used in other locations by TinyMCE (e.g. window.setTimeout). Also, document not being shimmed seemed to cause problems.
This works for us:
alias: {
'tinymce': 'tinymce/tinymce.jquery.js'
}
module: {
loaders: [
{
test: /tinymce\/tinymce\.jquery\.js/,
loader: 'imports?document=>window.document,this=>window!exports?window.tinymce'
}
]
}
Load your plugins like this:
{
test: /tinymce\/plugins/,
loader: 'imports?tinymce,this=>{tinymce:tinymce}'
}