Webpack ProvidePlugin vs externals? - backbone.js

I'm exploring the idea of using Webpack with Backbone.js.
I've followed the quick start guide and has a general idea of how Webpack works, but I'm unclear on how to load dependency library like jquery / backbone / underscore.
Should they be loaded externally with <script> or is this something Webpack can handle like RequireJS's shim?
According to the webpack doc: shimming modules, ProvidePlugin and externals seem to be related to this (so is bundle! loader somewhere) but I cannot figure out when to use which.
Thanks

It's both possible: You can include libraries with a <script> (i. e. to use a library from a CDN) or include them into the generated bundle.
If you load it via <script> tag, you can use the externals option to allow to write require(...) in your modules.
Example with library from CDN:
<script src="https://code.jquery.com/jquery-git2.min.js"></script>
// the artifial module "jquery" exports the global var "jQuery"
externals: { jquery: "jQuery" }
// inside any module
var $ = require("jquery");
Example with library included in bundle:
copy `jquery-git2.min.js` to your local filesystem
// make "jquery" resolve to your local copy of the library
// i. e. through the resolve.alias option
resolve: { alias: { jquery: "/path/to/jquery-git2.min.js" } }
// inside any module
var $ = require("jquery");
The ProvidePlugin can map modules to (free) variables. So you could define: "Every time I use the (free) variable xyz inside a module you (webpack) should set xyz to require("abc")."
Example without ProvidePlugin:
// You need to require underscore before you can use it
var _ = require("underscore");
_.size(...);
Example with ProvidePlugin:
plugins: [
new webpack.ProvidePlugin({
"_": "underscore"
})
]
// If you use "_", underscore is automatically required
_.size(...)
Summary:
Library from CDN: Use <script> tag and externals option
Library from filesystem: Include the library in the bundle.
(Maybe modify resolve options to find the library)
externals: Make global vars available as module
ProvidePlugin: Make modules available as free variables inside modules

Something cool to note is that if you use the ProvidePlugin in combination with the externals property it will allow you to have jQuery passed into your webpack module closure without having to explicitly require it. This can be useful for refactoring legacy code with a lot of different files referencing $.
//webpack.config.js
module.exports = {
entry: './index.js',
output: {
filename: '[name].js'
},
externals: {
jquery: 'jQuery'
},
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
})
]
};
now in index.js
console.log(typeof $ === 'function');
will have a compiled output with something like below passed into the webpackBootstrap closure:
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {
/* WEBPACK VAR INJECTION */(function($) {
console.log(typeof $ === 'function');
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1)))
/***/ },
/* 1 */
/***/ function(module, exports, __webpack_require__) {
module.exports = jQuery;
/***/ }
/******/ ])
Therefore, you can see that $ is referencing the global/window jQuery from the CDN, but is being passed into the closure. I'm not sure if this is intended functionality or a lucky hack but it seems to work well for my use case.

I know this is an old post but thought it would be useful to mention that the webpack script loader may be useful in this case as well. From the webpack docs:
"script: Executes a JavaScript file once in global context (like in script tag), requires are not parsed."
http://webpack.github.io/docs/list-of-loaders.html
https://github.com/webpack/script-loader
I have found this particularly useful when migrating older build processes that concat JS vendor files and app files together. A word of warning is that the script loader seems only to work through overloading require() and doesn't work as far as I can tell by being specified within a webpack.config file. Although, many argue that overloading require is bad practice, it can be quite useful for concating vendor and app script in one bundle, and at the same time exposing JS Globals that don't have to be shimmed into addition webpack bundles. For example:
require('script!jquery-cookie/jquery.cookie');
require('script!history.js/scripts/bundled-uncompressed/html4+html5/jquery.history');
require('script!momentjs');
require('./scripts/main.js');
This would make $.cookie, History, and moment globally available inside and outside of this bundle, and bundle these vendor libs with the main.js script and all it's required files.
Also, useful with this technique is:
resolve: {
extensions: ["", ".js"],
modulesDirectories: ['node_modules', 'bower_components']
},
plugins: [
new webpack.ResolverPlugin(
new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin("bower.json", ["main"])
)
]
which is using Bower, will look at the main file in each required libraries package.json. In the above example, History.js doesn't have a main file specified, so the path to the file is necessary.

Related

Export module via Webpack

I am working on a React app. In index.js am exporting some variables, e.g.
export const a="Hello";
export const b=[1,2,3];
In webpack.config.js:
...
output: {
...
library: "myApp",
libraryTarget: "window"
},
...
I know window.myApp is now a module which includes all the exported variables from index.js. What I do not know is how the above works. How this module is being created and why it includes only the exports from index.js and not other files as well? How, may I include exports from another specific file?
Your module is being created based on the entry configuration and the other modules and plugins that you configure and is converted to a build file based on the configuration provided in the output config of webpack.
library setup is tied to the entry configuration so if you specify the entry to be index.js, your exports from within the index.js are available within the build
In order to also expose exports from other files, you can import and export them from the index file like
export { default as SomeFunction} from 'some-function.js';
According to the webpack docs:
For
most libraries, specifying a single entry point is sufficient. While
multi-part libraries are possible, it is simpler to expose partial
exports through an index script that serves as a single entry point.
Using an array as an entry point for a library is not recommended.
libraryTarget specifies how the module is exposed. For instance in your case your module is exposed on the window object.
You can expose the library in the following ways:
Variable: as a global variable made available by a script tag (libraryTarget:'var').
This: available through the this object (libraryTarget:'this').
Window: available through the window object, in the browser (libraryTarget:'window').
UMD: available after AMD or CommonJS require (libraryTarget:'umd').
If library is set and libraryTarget is not, libraryTarget defaults to
var as specified in the output configuration documentation. See
output.libraryTarget there for a detailed list of all available
options.
You have two solution to include other files
First is to use multiple entries in your webpack module
module.exports = {
// mode: "development || "production",
entry: {
A: "./CompA",
B: "./CompB"
},
Second approach is use one entry like your solution, but need to add wrappere.js
wrapper.js
import * as a from './CompA.js'
import * as b from './CompB.js'
export a,b
And your wrapper become the single entry, where you can access later wrapper.a,wrapper.b
module.exports = {
// mode: "development || "production",
entry: {
app: "./wrapper.js",
},

How to configure the webpack configurations in create-react-app

I have a question or problem.
I'm using React v.16 so when I create a project I did with create-react-app that webpack is already preconfigured. And I want work with ol-cesium, and in npmjs I see that I have to:
create an alias to the goog directory. With webpack:
resolve: {
alias: {
'goog': path_to_goog,
}
}
If I dont create a webpack file show me this error:
./node_modules/olcs/AbstractSynchronizer.js
107:22-35 "export 'getUid' (imported as 'olBase') was not found in 'ol/index.js'
How can solve it??? And what is path_to_goog???
EDIT
Thanks to Shishir Anshuman for your help.
Now I add alias on webpack.config.dev.js and webpack.config.prod.js but some me a lot errors.
resolve: {
// This allows you to set a fallback for where Webpack should look for modules.
// We placed these paths second because we want `node_modules` to "win"
// if there are any conflicts. This matches Node resolution mechanism.
// https://github.com/facebookincubator/create-react-app/issues/253
modules: ['node_modules', paths.appNodeModules].concat(
// It is guaranteed to exist because we tweak it in `env.js`
process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
),
// These are the reasonable defaults supported by the Node ecosystem.
// We also include JSX as a common component filename extension to support
// some tools, although we do not recommend using it, see:
// https://github.com/facebookincubator/create-react-app/issues/290
// `web` extension prefixes have been added for better support
// for React Native Web.
extensions: ['.web.js', '.mjs', '.js', '.json', '.web.jsx', '.jsx'],
alias: {
// Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
'react-native': 'react-native-web',
// Ol-Cesium
'goog': '../node_modules/olcs/goog',
},
plugins: [
// Prevents users from importing files from outside of src/ (or node_modules/).
// This often causes confusion because we only process files within src/ with babel.
// To fix this, we prevent you from importing files out of src/ -- if you'd like to,
// please link the files into your node_modules/ and let module-resolution kick in.
// Make sure your source files are compiled, as they will not be processed in any way.
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
],
},
In console show me this error:
./node_modules/olcs/AbstractSynchronizer.js
107:22-35 "export 'getUid' (imported as 'olBase') was not found in 'ol/index.js'
__stack_frame_overlay_proxy_console__ # index.js:2178
handleErrors # webpackHotDevClient.js:178
./node_modules/react-dev-utils/webpackHotDevClient.js.connection.onmessage # webpackHotDevClient.js:211
./node_modules/sockjs-client/lib/event/eventtarget.js.EventTarget.dispatchEvent # eventtarget.js:51
(anonymous) # main.js:274
./node_modules/sockjs-client/lib/main.js.SockJS._transportMessage # main.js:272
./node_modules/sockjs-client/lib/event/emitter.js.EventEmitter.emit # emitter.js:50
WebSocketTransport.ws.onmessage
In the Codesandbox provided by you, I was unable to find the root cause, but I noticed the following:
I noticed that you have used the ES6 import statement:import OLCesium from "olcs/OLCesium";.
But as per this issue, the module is not yet ported to ES6.
I have never used this library before, So it's hard to figure out what exactly is going on.
Did you try installing https://www.npmjs.com/package/geom ? Since the error says 4.6.4/geom/Point.js is missing.

Require.js can't recognize modules in concatenated JS file

I am integrating Require.js to AngularJS based web application for performance improvement.
I've imported require.conf in index.html:
<script src="bower_components/requirejs/require.js" data-main="require-conf.js">
Here is code snippet of require-conf.js:
require.config({
paths: {
'jquery': '...',
'Angular': '...',
....
'libs' : 'src/libs.js'
},
shim: {
'Angular': { exports: 'Angular'},
'libs' : ['Angular']
....
}
}
require(
[
'jquery',
'angular',
'app',
'libs',
], function (jquery, angular) {
angular.bootstrap(['app'])
}
);
Here, libs.js is the library I've built by webpack. Some plugins and libraries are concatenated in this file.
Here is webpack configuration code snippet to build libs.js.
In webpack.config.js
plugins: [
new ConcatPlugin({
fileName: 'libs.js',
filesToConcat: [
'./src/utils/bootstrap-plugins.min.js',
'./src/libs/angular-bootstrap-datetimepicker/datetimepicker.js',
'./src/libs/angular-bootstrap-datetimepicker/datetimepicker.templates.js',
'./src/libs/angular-fusioncharts/fusioncharts.js',
'./src/libs/angular-fusioncharts/angular-fusioncharts.min.js',
'./src/libs/angular-fusioncharts/types/fusioncharts.charts.js',
'./src/libs/angular-ui-tour/angular-ui-tour.js',
'./src/libs/JQ_CONFIG/flot/jquery.flot.js',
'./src/libs/JQ_CONFIG/flot/jquery.flot.pie.js',
'./src/libs/JQ_CONFIG/flot/jquery.flot.resize.js',
'./src/libs/JQ_CONFIG/flot-spline/js/jquery.flot.spline.min.js',
'./src/libs/JQ_CONFIG/flot.orderbars/js/jquery.flot.orderBars.js',
'./src/libs/JQ_CONFIG/flot.tooltip/js/jquery.flot.tooltip.min.js',
'./src/libs/JQ_CONFIG/footable/dist/footable.all.min.js',
'./src/libs/JQ_CONFIG/html5sortable/jquery.sortable.js',
'./src/libs/jquery-ui-draggable/jquery-ui-draggable.min.js',
'./src/libs/ng-table/ng-table.js',
'./src/libs/StickyTableHeaders/jquery.stickytableheaders.js',
'./src/libs/ng-quill/quill.js',
'./src/libs/ng-quill/ng-quill.js',
].map(function(fileName) {
return path.resolve(__dirname, fileName);
}),
But, App can't recognize the modules inside libs.js:
Module 'ngquill' is not available! you either misspelled the module
name or forgot it to load it. If registering a module ensure that you
specify the dependencies as the second argument.
Require.js can't recognize the modules concatenated by webpack? Is there any solutions to fix this problem?
You cannot combine AMD modules into a single file just by concatenating them into a single file. When you combine multiple modules into a single file, the modules must get hardcoded names. When you have a single module in a single file, the define for it can be:
define([ ... deps ... ], function (...) {
In this case, RequireJS infers the name of the module from the name under which it was requested.
When you combine multiple modules in a single file, the define calls must be of the form:
define("foo", [ ... deps ...], function (...) {
define("bar", [ ... deps ...], function (...) {
// etc.
The first argument to define is a string, which tells RequireJS which module is being defined. This is necessary because otherwise RequireJS won't know which module is which. This is why you cannot just concatenate.
You most likely could write a Webpack configuration that could both transform the files as I described above and concatenate them. However, that's rife with hurdles. For instance the runtime shim configuration require special handling at build time. In the end you may end up replicating the functionality of RequireJS' optimizer. I would suggest using RequireJS' optimizer instead of reinventing the wheel.

How can I create a configuration file for react and prevent webpack bundling it?

I added a config.json to application.
in webpack.config.js I defined Config
externals: {
'Config': JSON.stringify(production ? require('./config.prod.json') : require('./config.dev.json'))
},
in application I required config and used it
var Config = require('Config');
However, webpack bundles my config file into index.js(my webpack output file) and I dont want this.
I want to keep my config.json seperate from index.js To achieve this, I excluded my config.json but it did not work.
exclude: [/node_modules/, path.resolve(__dirname, 'config.dev.json'), path.resolve(__dirname, 'config.prod.json')]
Can you please help me if I miss something.
Thanks
As descibed by #thedude you can use webpack's code splitting feature.
Instead of simply doing import config from 'config.json' you can use a really cool feature of code splitting.
require.ensure([], function(require) {
let _config = require("config.json");
this.setState({
_configData: _config
});
});
and when you want to use data of config, do that by checking state
if(this.state._configData) { // this checks _configData is not undefined as well
// use the data of config json
}
When you will compile your code using webpack then two bundle files will be created i.e. bundle.js and 0.bundle.js. This 0.bundle.js has your code of config.json file.
You should use webpack's code splitting feature: https://webpack.js.org/guides/code-splitting/#src/components/Sidebar/Sidebar.jsx

How to solve webpack2 error: You may need an appropriate loader to handle this file type?

I am trying to convert my reactjs/webpack/gulp app to webpack2. I am using the webpackconfig+.babelrc file and package.json from this project as a starting point:
https://github.com/ModusCreateOrg/budgeting-sample-app-webpack2
This is the gulpcode:
var gulp = require('gulp');
var webpackStream = require('webpack-stream');
var webpack2 = require('webpack');
gulp.task('default', function() {
return gulp.src('app/app.js')
.pipe(webpackStream({/* options */}, webpack2))
.pipe(gulp.dest('dist/'));
});
When I run 'gulp' I get this error:
stream.js:74
throw er; // Unhandled stream error in pipe.
^
Error: ./app/app.js
Module parse failed: C:\myapp\app\app.js Unexpected token (11:4)
You may need an appropriate loader to handle this file type.
|
| ReactDOM.render(
| <Provider>
| <App />
| </Provider>,
Which loader do I need and where do I configure this?
You're not actually using webpack.config.js so presumably you're configuring the options in the Gulpfile. But you commented the options as /* options */ which is the important bit, where you actually configure webpack.
You configure the loaders in these options under module.rules (see also Concepts - Loaders). In your case you need the babel-loader with babel-preset-react to be able to transpile JSX syntax, the preset should already be in the .babelrc config you're using. You just need to add the loader to your options in the Gulpfile so that your .js and .jsx files are passed through that loader (using the same rule as in the project you linked):
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: [
'babel-loader'
],
}
]
}
Instead of defining the options in your Gulpfile you can define a webpack.config.js and then just require it and pass it to webpackStream. This file is automatically used when you use webpack directly (without Gulp), so it's very convenient to have that config if you ever want to run it outside of Gulp. It's good idea to get familiar with webpack. A good starting point is the Core Concepts of the official docs.
Usually an object is exported, which you can use directly in webpackStream, but in the project you linked, it exports a function which returns the configuration object based on the environment used. To use it directly in your Gulpfile you would do the following:
var gulp = require('gulp');
var webpackStream = require('webpack-stream');
var config = require('./webpack.config');
gulp.task('default', function() {
return gulp.src('app/app.js')
.pipe(webpackStream(config()))
.pipe(gulp.dest('dist/'));
});
config() returns the development options and to get the production config you'd need to call config({ prod: true }). You probably don't want to use exactly that config but use your own which you can just export as a plain object, so you don't need to call a function.

Resources