How to Bundle Your JavaScript Projects Using webpack
Scenario
I use webpack to bundle some JS scripts today, see openapi-scripts/pull/16. The following lists details of the project and reasons for bundling:
- The
openapi-scripts
repository is used to post-process the OpenAPI specification file generated bybuf.build
to be compatible with Redocly. - To use JS scripts, I build a Docker image
ghcr.io/oreoxmt/openapi-scripts
. - The Docker image is under rapid development, and it takes a long time to pull the latest image due to 3901 node modules.
Step 1. Install webpack-cli
To install webpack-cli
, run the following command. For more information about the directory structure, refer to Basic setup.
- yarn
- npm
yarn add webpack webpack-cli --dev
npm install webpack webpack-cli --save-dev
Step 2. Configure a webpack.config.js
For more details about configuration in webpack, see Configuration.
-
Create a
webpack.config.js
file in the root directory of the repository. -
Then, configure the
webpack.config.js
file.infoThe entry is
./src/main.js
in the repository.const path = require('path');
module.exports = {
entry: './src/main.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
},
}; -
Use the preceding configuration file to generate a
dist/main.js
file.- yarn
- npm
npx webpack --config webpack.config.js
npx webpack --config webpack.config.js
-
Oops! An error occurs after executing the preceding command.
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "http": require.resolve("stream-http") }'
- install 'stream-http'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "http": false }
@ ./node_modules/@apidevtools/json-schema-ref-parser/lib/options.js 9:21-48
@ ./node_modules/@apidevtools/json-schema-ref-parser/lib/normalize-args.js 3:16-36
@ ./node_modules/@apidevtools/json-schema-ref-parser/lib/index.js 6:22-49
@ ./src/deref.js 2:19-65
@ ./src/main.js 3:14-35 -
Then, I Google the error message and find the answer in Stack Overflow and Targets in webpack. To use
node
webpack to compile for usage, add thetarget: 'node'
into thewebpack.config.js
file.const path = require('path');
module.exports = {
target: 'node',
entry: './src/main.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
}
}; -
Try to execute
npx webpack --config webpack.config.js
again and a warning message shows:$ npx webpack --config webpack.config.js
asset main.js 484 KiB [emitted] [minimized] (name: main) 1 related asset
...
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value.
Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
webpack 5.74.0 compiled with 1 warning in 1990 ms -
To enables deterministic mangled names for modules and chunks, add
mode: 'production'
into thewebpack.config.js
file.const path = require('path');
module.exports = {
target: 'node',
entry: './src/main.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
},
mode: 'production',
}; -
Try to execute
npx webpack --config webpack.config.js
again and it works to generate adist/main.js
anddist/main.js.LICENSE.txt
.$ npx webpack --config webpack.config.js
...
json modules 155 KiB
modules by path ./node_modules/har-schema/lib/ 6.93 KiB 18 modules
modules by path ./node_modules/ajv/lib/ 5.58 KiB
./node_modules/ajv/lib/refs/json-schema-draft-06.json 2.46 KiB
+ 2 modules
./node_modules/mime-db/db.json 143 KiB
webpack 5.74.0 compiled successfully in 2524 ms -
To test the generated
dist/main.js
file, run the following command.$ node dist/main.js --help
Usage: postprocess [options] [command]
Postprocess an OpenAPI document for ReDoc
Options:
-V, --version output the version number
-h, --help display help for command
Commands:
deref <in-filename> [out-filename] Use $RefParser to dereference a JSON schema
importmd <in-filename> <md-folder> <gen-md> Merge markdown files in <md-folder> to a markdown <gen-file>, and import
it as $ref to JSON info.description.
addlogo <in-filename> <url> <alt> <href> Add a logo to JSON info.x-logo
gencode <in-filename> Generate sample code to JSON as x-code-samples
devtier <in-filenam> Add sample for creating a dev tier cluster
help [command] display help for command
Step 3. Modify the Dockerfile
Modify the Dockerfile as follows:
- original Dockerfile
- modified Dockerfile
FROM node:16
WORKDIR /app
COPY package.json yarn.lock ./src/ .
RUN yarn install
ENTRYPOINT ["node", "/app/main.js"]
FROM node:16 AS build
WORKDIR /build
COPY . .
RUN yarn install && yarn run build
FROM node:16
COPY /build/dist/main.js ./
ENTRYPOINT ["node", "main.js"]
-
The new Dockerfile contains two stages. For more information about multi-stage builds, refer to Use multi-stage builds.
-
The first stage is a
build
stage, which tells Docker to:-
build an image starting with the node:16 image and name it
build
-
copy all files in the repository to the
/build
foldertipTo ignore some files, you can create a soft link from
.gitignore
to.dockerignore
usingln
:ln -s .gitignore .dockerignore
-
run
yarn install
andyarn run build
The
yarn run build
command is equivalent tonpx webpack --config webpack.config.js
, which is thescripts
set inpackage.json
. Besides, movedependencies
intodevDependencies
as follows:- original package.json
- modified package.json
{
"name": "openapi-scripts",
...
"devDependencies": {
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0"
},
"scripts": {},
...
"dependencies": {
"@apidevtools/json-schema-ref-parser": "^9.0.9",
"commander": "^9.3.0",
"openapi-snippet": "^0.14.0"
}
}{
"name": "openapi-scripts",
...
"devDependencies": {
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"@apidevtools/json-schema-ref-parser": "^9.0.9",
"commander": "^9.3.0",
"openapi-snippet": "^0.14.0"
},
"scripts": {
"build": "webpack --config webpack.config.js"
},
...
}
-
-
The second stage is a result stage, which builds an image for production.
Step 4. Build the Docker image
To build an image for testing, run the following command to build a test
image:
$ docker build -t test .
[1/2] STEP 1/4: FROM node:16 AS build
[1/2] STEP 2/4: WORKDIR /build
--> 73bbc8c7552
[1/2] STEP 3/4: COPY . .
--> 53e13c02e1a
[1/2] STEP 4/4: RUN yarn install && yarn run build
yarn install v1.22.19
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
Done in 22.27s.
yarn run v1.22.19
$ webpack --config webpack.config.js
...
Done in 6.36s.
--> 5aa7f288822
[2/2] STEP 1/3: FROM node:16
[2/2] STEP 2/3: COPY --from=build /build/dist/main.js ./
--> 0c7bf448572
[2/2] STEP 3/3: ENTRYPOINT ["node", "main.js"]
[2/2] COMMIT test
--> 9dd21695569
Successfully tagged localhost/test:latest
Start a container with the test
image and run the ls
command to see files in container:
$ docker run -i -t --entrypoint bash 9dd2
root@007:/# ls -la
total 484
dr-xr-xr-x. 1 root root 28 Aug 6 06:43 .
dr-xr-xr-x. 1 root root 28 Aug 6 06:43 ..
drwxr-xr-x. 1 root root 179 Aug 2 01:26 bin
drwxr-xr-x. 2 root root 6 Mar 19 13:44 boot
drwxr-xr-x. 5 root root 360 Aug 6 06:43 dev
drwxr-xr-x. 1 root root 31 Aug 6 06:43 etc
drwxr-xr-x. 1 root root 18 Aug 2 04:21 home
drwxr-xr-x. 1 root root 42 Aug 2 01:26 lib
-rw-r--r--. 1 root root 495164 Aug 7 2022 main.js
drwxr-xr-x. 2 root root 6 Aug 1 00:00 media
drwxr-xr-x. 2 root root 6 Aug 1 00:00 mnt
drwxr-xr-x. 1 root root 27 Aug 2 04:24 opt
dr-xr-xr-x. 161 nobody nogroup 0 Aug 6 06:43 proc
drwx------. 1 root root 20 Aug 2 04:24 root
drwxr-xr-x. 1 root root 42 Aug 6 06:43 run
drwxr-xr-x. 1 root root 20 Aug 2 01:26 sbin
drwxr-xr-x. 2 root root 6 Aug 1 00:00 srv
dr-xr-xr-x. 12 nobody nogroup 0 Aug 5 00:14 sys
drwxrwxrwt. 1 root root 32 Aug 2 04:24 tmp
drwxr-xr-x. 1 root root 19 Aug 1 00:00 usr
drwxr-xr-x. 1 root root 17 Aug 1 00:00 var
It works!