Webpack and clientside module loading - javascript

I'm comparing how RequireJS and Webpack works, and there's something I still don't get:
RequireJS is loaded in the browser through a <script> tag, e.g.:
<script data-main="scripts/main.js" src="scripts/require.js"></script>
In its simplest implementation, it uses the data-main attribute to set its base path and loads asynchronously every js in it
Webpack works in Nodejs environment. You install it via npm install cmd. Then you set the entry point and other stuffs in the webpack.config file
module.exports = {
entry: './src/app.js',
output: {
path: './bin',
filename: 'app.bundle.js'
}
};
When you run webpack cmd your entry point is read and its dependencies are loaded and bundled together in the output file (in this case, app.bundle.js).
This two behaviors seems to me really differents (RequireJS run and loads in browser, Webpack in Node), and I don't understand why this two module system are mentioned as interchangeable.
In particular it seems to me that webpack doesn't do anything special, it only concatenates a bunch of js together. What am I missing?

Related

bundling multiple js files

in react using webpack every js files is bundle into a single bundle.js , for my normal html , css, js application for example , i am having 6 libraries. for an example consider
i am using jquery and bootstrap min versions. so if i reference two files the request will be two. so how can i make it into a single file. So there will be a single request.
like when i checked the file size is about in kb's and the request is processed within less that 1 or 2 seconds , like the chrome dev tools shows the time for to load also it parrallely loads the two files.
But how can i bundle the two librarys using webpack and get a single file that i can refer in my application.
i am a beginner to webpack
You need to import them in your entry point file and Webpack will handle the bundling. As you have worked with React, I assume you have basic command line skills.
You can read the Getting Started guide which bundles Lodash like how you are trying to bundle jQuery and Bootstrap.
First of install, ensure that you are installing jQuery, Bootstrap, and any other libraries using npm (or yarn, if you prefer):
# Install Webpack as a dev dependency
npm install webpack webpack-cli --save-dev
# Install dependencies (I've added Popper.js as Bootstrap requires it)
npm install jquery bootstrap popper.js
Create a folder called src and a file inside there called index.js. This is your entry point and Webpack will look for this file unless configured differently. Import the libraries like this:
import $ from 'jquery'
import 'bootstrap'
// Do something with jQuery
$(document).ready(() => console.log('Hello world!'))
Then run Webpack using npx:
npx webpack
A file named main.js should be created in a folder called dist that contains the bundled code. This is your output file. You can use a <script> tag in your HTML file to load this JavaScript:
<!-- assuming your index.html is in the dist folder -->
<script src='main.js'></script>
Once you get here, you can explore more advanced things like importing Bootstrap components individually, minifying code, multiple bundles, transpiling TypeScript, etc.
You will likely need to add a Webpack configuration file very soon as there is only so much that can be done using zero-config mode.
Good practice is to keep two sepearate bundles for the application logic and external libraries and in webpack this can be achieved by the following code,
app.js - appliation index file,
vendors.js - import all external libraries in this file
entry: {
app: './src/app.js',
vendors: './src/vendors.js'
}
To get a single file, import vendors.js file inside app.js file and give entry key in webpack as
entry: './src/app.js'
Let us assume that you have the files in src directory. You can merge multiple files by specifying them in webpack.config.js to have a single named file as an output. I hope this is what you are looking for.
const path = require('path');
module.exports = {
entry: {
'bundle.js': [
path.resolve(__dirname, 'src/file1.js'),
path.resolve(__dirname, 'src/file2.js')
]
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [{
exclude: /node_modules/
}]
}
};
As above, the two files "file1.js" and "file2.js" will be combined into a single file "bundle.js" and stored in "dist" directory.
You can also exclude node_modules by specifying a rule in module object of webpack configuration.

Bundling Jquery and other modules with webpack

Noob here (also couldn't find proper documentation).
So I was trying to implement gulp.js with jekyll. In order to do so, I wanted to concat javascript files into a single bundle. Now I can do that by hand, hard-coding every dependency and piping it through gulp-concat. But, I found out webpack does this thing pretty neatly. (PS: I was following https://ixkaito.github.io/frasco/). So now I installed webpack via npm, and tried to run my site, but it threw uncaught expression error. My directory tree is like this:
-js
----vendor
-------jquery.js
-------anime.js
----other
-------some-other-js-files.js
...
-main.js
Now I want to make bundle.js files using this, so that webpack can automatically detect the correct dependency and import it. Am I supposed to require('jquery') and do the same for all dependency in main.js?
My webpack config is
entry: [
"main.js",
]
Thanks

Is there a way I can change any of the files shown here to deploy with webpack?

What's coming? (1) Short background. (2) Question. (3) Detail (short; file listings just long enough to show relevant information). (4) Question again.
Short background: I want to deploy my web site using webpack. webpack is looking for style-loader and css-loader in the wrong directories, so my builds aren't completing.
Question
Is there a way I can change any of the files shown here to deploy with webpack?
Detail
environment
Windows 10 Home -64, up-to-date/ Toshiba Satellite with AMD A6
Node v6.2.0/ webpack v1.13.2
relevant files and directory structure
C:\Dev\example\example.js
C:\Dev\example\bluebird.js
C:\Dev\example\jquery.js
C:\Dev\example\img\image1.jpg
C:\Dev\example\img\image2.jpg
C:\Dev\example\img\svg1.svg
C:\Dev\example\built\package.json
C:\Dev\example\built\webpack.config.js
C:\Dev\example\built\index1.html
C:\Dev\example\built\index2.html
C:\Dev\example\built\example.bundle.js
C:\Dev\example\built\[hash1].jpg
C:\Dev\example\built\[hash2].jpg
C:\Dev\example\built\[hash3].svg
C:\Dev\node-modules\webpack
C:\Dev\node-modules\css-loader
C:\Dev\node-modules\file-loader
C:\Dev\node-modules\html-loader
C:\Dev\node-modules\style-loader
C:\Dev\node-modules\uglify-js
C:\Dev\node-modules\url-loader
C:\Dev\example\built\package.json
{
"name": "example",
"version": "0.0.1",
"description": "example",
"main": "example.bundle.js",
"author": "Bald Eagle"
}
call to webpack: I call webpack at the command prompt from C:\Dev\example\built with one line (two here for your convenience)
node C:\Dev\node_modules\webpack\bin\webpack.js -p --display-reasons
--display-error-details --display-modules --profile
C:\Dev\example\built\webpack.config.js
"use strict"
let path = require("path")
let webpack = require("webpack")
let preferEntry = true // for OccurrenceOrderPlugin
module.exports = {
context: "C:/Dev/example/built",
entry: [
"./index1.html",
"./../jquery.js",
"./../bluebird.js",
"./../example.css"
],
output: {
path: "C:/Dev/example/built",
filename: "./example.bundle.js",
publicPath: "http://www.example.com"
},
module: {
loaders: [
{
test: /\.(?:gif|jpg|jpeg|svg)$/,
loader: "file!url"
},
{
test: /\.html$/,
loader: "html"
},
{
test: /\.png$/,
loader: "url-loader?mimetype=image/png"
},
{
test: /\.css$/,
loader: "style-loader!css-loader"
} ],
plugins: [
new webpack.optimize.UglifyJsPlugin({minimize: true}),
new webpack.ProvidePlugin({
'window.jQuery': 'jquery',
'window.$': 'jquery',
}) ] },
resolve: {
root: path.resolve("./index1.html"),
modulesDirectories: ["node_modules", "built"],
fallback: "C:/Dev"
},
resolveLoader: {
fallback: "C:/Dev",
modulesDirectories: ["node_modules"]
}
}
C:\Dev\example\built\index1.html: My goal for this HTML file is to point webpack the right direction to collect all information for the web site into its example.bundle.js file. I shortened one long line (clip) and excluded irrelevant lines, including the entire body.
<!doctype html>
<html>
<head>
<script src="./../example.js"></script>
<link href="./../example.css" rel="stylesheet" type="text/css" />
<link href="https://fonts.googleapis.com/css? (clip) type="text/css" />
<script src="./../jquery.js"></script>
<script src="./../bluebird.js"></script>
</head>
<body>
</body>
</html>
feedback from webpack
It outputs routine information about building pieces of example.bundle.js (represented with ...), except these: (I added any double asterisks **; I broke some long lines and indented the later parts of such lines.)
...
[9] ./../example.css 919 bytes {0} [built] [**2 errors**]
single entry ./../example.css [0] multi main
WARNING in ./example.bundle.js from UglifyJs
...
Condition always false [C:/Dev/~/style-loader!
C:/Dev/~/css-loader!**./../example.css:10,0**]
Dropping unreachable code [C:/Dev/~/style-loader!
C:/Dev/~/css-loader!**./../example.css:12,0**]
Side effects in initialization of unused variable update
[C:/Dev/~/style-loader!C:/Dev/~/css-loader!
**./../example.css:7,0**]
**ERROR** in ./../example.css
Module not found: Error: Cannot resolve 'file' or 'directory'
./../node_modules/css-loader/index.js in
C:/Dev/example/built\..
resolve file
**C:/Dev/example/built\node_modules\css-loader\index.js
doesn't exist**
[plus more similarly inaccurate variants; it should be looking
for C:\Dev\node_modules\css-loader\index.js; it's there]
...
**ERROR** in ./../example.css
Module not found: Error: Cannot resolve 'file' or 'directory'
./../node_modules/style-loader/addStyles.js in
C:/Dev/example/built\..
resolve file
**C:/Dev/example/built\node_modules\style-loader\addStyles.js.webpack.js
doesn't exist**
[plus more similarly inaccurate variants]
...
With the double asterisks, I wanted to highlight that webpack outputted that there'd be two errors and outputted the two errors. Also, I wanted to highlight the cited points of error in css. Those lines are below.
C:\Dev\example\example.css: I show only the first twelve lines because of the lines webpack cited as errors; I added line numbers.
1 /* example web site
2 * blah
3 * blah
4 * blah
5 * blah
6 * blah
7 *
8 * Version
9 */
10
11 /* defaults */
12 body, div {
My conclusion: The points webpack cited are not points of error. Maybe the points cited apply to a minified version of the file. Or to some code.
Problem when I point a browser to index1.html above: All content and rendering are fine; JavaScript isn't enabled. (Consistent with not processing css correctly?)
C:\Dev\example\built\index2.html: My goal for this HTML file is to use the same webpack techniques I'll use in the index.html that will be on the web server. I changed this the same ways I changed index1.html.
<!doctype html>
<html>
<head>
<script src="./example.bundle.js"></script>
<link href="https://fonts.googleapis.com/css? (clip) type="text/css" />
</head>
<body>
</body>
</html>
Problem when I point a browser to index2.html immediately above: All content shows (including the three images), but (1) JavaScript isn't enabled and (2) there's no effect from css (no colors or positioning, etc.) (Consistent with not processing css correctly?)
Question
Is there a way I can change any of the files shown here to deploy with webpack?
Thanks for taking the time to read. Thanks in advance for any responses.
Need more detail? I'll provide it if I can.
It seems like you've set up your project in a very unusual style for Node.js development which makes your setup more complicated than it needs to be. I'm not sure if that is a hard requirement, but since you've not mentioned it to be required, I'm assuming you are allowed to change your directory structure.
Use local dependencies
In Node.js, all dependencies are installed separately for each project. It enables you to use different versions of a dependency while working on different projects without the need to switch these dependencies manually. You should put the package.json in the root folder of your project (I assume C:\Dev\example in your case) and then run npm install webpack css-loader style-loader --save-dev. NPM is the package manager for Node.js that comes pre-bundled with the executable so it should already be installed on your system. After running npm install you should see a node_modules folder which contains all these project-specific dependencies. You should not commit these dependencies to your version control system because they may contain platform-specific binaries that are not usable on different machines. Every developer in this project needs to run npm install first.
Configure your common commands under scripts
NPM provides the possibility to add common commands as scripts to your package.json. This way, other developers (including "Future You") only need to remember npm run some-script instead of all the command-line options. One little known feature of NPM is, that it prepends ./node_modules/.bin to your $PATH on runtime which means that inside scripts you can reference any executable installed in your local node_modules just as if it was installed globally. Thus you only need to write:
"scripts": {
"start": "webpack --config ./path/to/webpack.config.js"
}
and it will use your local webpack installation. As a side-note: It's best-practice to provide a "start" script. It can be called by just running npm start.
Your webpack config
Your webpack config looks ok, but I have some advice:
You should separate between your development stuff and the actual build. Usually, the webpack.config.js is placed in the root of your project because then you just need to run webpack and webpack will look for a config in the current directory. Then, you should have a src or app folder which contains all the development files, including the index HTML files as you've mentioned. At last, you create a build folder which is completely wiped before a build. This way, your build does not contain any obsolete files.
Do not use absolute paths in your webpack.config. It makes sharing your project a lot harder – including "Future You" that moves the project to another directory. Use the Node.js builtin path module and the special module variable __dirname to resolve absolute paths. You can also use require.resolve to use Node's resolving algorithm.
Do not set the context and the publicPath option unless you know what you are doing. Given from the information you've provided, I doubt that these options are necessary here.
Since you've installed all the dependencies locally, you should remove all the resolving options because it should work out-of-the-box now.
Although it is permitted, you should not omit the -loader postfix in your loader configurations. It can lead to hard-to-understand errors when you have a css or html module in your node_modules just by coincidence.
The problem with index.html files
webpack is a JavaScript bundler. That's why you need to have a css- or an html-loader if you want to include HTML or CSS. These loaders transform HTML or CSS into a JavaScript module by transforming the text contents into strings that are exported. This has some nice advantages like hot module replacement or CSS modules. But it also means that you can't use an HTML or CSS file as the only entry point to your website. Every HTML and CSS will eventually be translated to JavaScript. This trade-off is ok for single-page applications where you need JS anyway, but it is probably surprising for everyone who tries to use webpack on a static site.
Since you're not the first one with this problem, several solutions have already emerged. My recommendation is to use the HTML Webpack Plugin in combination with the Extract Text Webpack Plugin.
The Extract Text Webpack Plugin can be used to extract static JS strings out of the bundle into a speparate file. This is exactly what you need to remove CSS from the JS bundle.
The HTML Webpack Plugin creates an index HTML file that includes all the generated JS and CSS files. Out of the box, it will just generate an HTML file with no contents (often referred to application shell which is typical for single-page applications). Since you already have content in your index HTML files, you need to configure your HTML files as templates. You'll need to add a plugin for each HTML file.
These plugins work pretty well, but this setup is already pretty complex. You should decide if you want to use webpack just for JS or also for HTML and CSS.

Make pdf.js 1.5 and require.js play nice together

In my project I have long used require.js together with the pdf.js library. Pdf.js have until recently been putting itself on the global object. I could still use it in my requirejs config by using a shim. The pdfjs library will in turn load another library called pdf.worker. In order to find this module the solution was to add a property to the global PDFJS object called workerSrc and point to the file on disk. This could be done before or after loading the pdfjs library.
The pdfjs library uses the pdf.worker to start a WebWorker and to do so it needs the path to a source file.
When I tried to update the pdfjs library in my project to a new version (1.5.314) the way to load and include the library have changed to use UMD modules and now everything get's a bit tricky.
The pdfjs library checks if the environment is using requirejs and so it defines itself as a module named "pdfjs-dist/build/pdf". When this module loads it checks for a module named "pdfjs-dist/build/pdf.worker". Since I have another folder structure I have added them to my requirejs config object with a new path:
paths: {
"pdfjs-dist/build/pdf": "vendor/pdfjs/build/pdf",
"pdfjs-dist/build/pdf.worker": "vendor/pdfjs/build/pdf.worker"
}
This is to make the module loader to find the modules at all. In development this works great. When I try to use the requirejs optimizer in my grunt build step however, it will put all of my project files into one single file. This step will try to include the pdf.worker module as well and this generates an error:
Error: Cannot uglify2 file: vendor/pdfjs/build/pdf.worker.js. Skipping
it. Error is: RangeError: Maximum call stack size exceeded
Since the worker source needs to be in a single file on disk I don't want this module to be included.
So I've tried two different config-settings in the requirejs config.
The first attempt was to override the paths property in my grunt build options:
paths: {
"pdfjs-dist/build/pdf.worker": "empty:"
}
The second thing to test is to exclude it from my module:
modules: [{
name: "core/app",
exclude: [
"pdfjs-dist/build/pdf.worker"
]
}]
Both techniques should tell the optimizer not to include the module but both attempts ended up with the same error as before. The requirejs optimizer still tries to include the module into the build and the attempt to uglify it ends up with a RangeError.
One could argue that since the uglify step fails it will not be included and I can go about my bussiness, but if the uglify step should happen to start working at a new update of pdfjs - what then?
Can anyone help me figure out why the requirejs config won't just exclude it in the build step and how to make it do so.
I found out what the core of my problem was and now I have a way to solve the problem and make my build process to work. My build step in grunt is using grunt-contrib-requirejs and I needed to override some options in the config for this job.
I didn't want the pdf.worker module to be included in my concatenated and minified production code.
I didn't want r.js to minify it only to later exclude it from the concatenated file.
I tried to solve the first problem thinking that it would mean that the second problem also should be solved. When I figured out the two were separate I finally found a solution.
In the r.js example on github there is a property named fileExclusionRegExp. This is what I now use to tell r.js not to copy the file over to the build folder.
fileExclusionRegExp: /pdf.worker.js/
Second, I need to tell the optimizer to not include this module in the concatenated file. This is done by overriding the paths property for this module to the value of "empty:".
paths: {
"pdfjs-dist/build/pdf.worker": "empty:"
}
Now my grunt build step will work without errors and all is well.
Thanks to async5 for informing me about the bug with uglify and the pdf.worker. The workaround is applied in another grunt task that uglify the worker and copies it into the build-folder separately. The options object for the grunt-contrib-uglify task will need this property in order to not break the pdf.worker file:
compress: {
sequences: false
}
Now my project works great when built for production.

require.js optimization: deep dependencies

I'm trying to optimize (i.e. build) my app which is dependent from ember-data. So as the path for ember-data I set this file https://github.com/emberjs/data/blob/master/packages/ember-data/lib/main.js but r.js includes only this particular file and omit it's dependencies. I thought maybe it's because of some kind of paths issue but I can't see any error messages in console.
Make sure that in your build file (the one you pass to r.js as a build config), you have:
findNestedDependencies: true
R.js is omitting so called "inline dependencies" by default so if you do have in your main.js i.e.
require(["sth"],function(){
require(["sthelse"]);
});
R.js will only load "sth" by assuming that "sthelse" should be loaded during runtime.
; )

Categories