Node module cross build targets for deployment in npm - javascript

I have my ES6 module that I want to build to target different environments. I don't know how to do this or if I should use webpack or rollup.
Build targets
ES6 environments like Vue.
Commonjs like node backend or some webpack builds.
The browser, the script tag.
Project directory structure
src
--Dog.js
index.js
package.json
Project Files
Dog.js
class Dog{
//Typical Dog stuff
}
export default Dog;
index.js
import Dog from "./src/Dog";
export {
Dog
}
webpack.config.js
module.exports = {
target: 'node',
mode: 'production',
};
package.json (relevant parts)
"files": [
"dist",
"src"
]
Is there any way to automatize this process or should I just write a new library for each target manually? And if there is how the projects that import my module know which build is right for their environment?

Just use different webpack configs for different environments and targets. It is a common approach.

This is what i think it works with a minimal webpack configuration. The dist folder and its contents are created by webpack.
Project directory structure
src
--Dog.js
dist
--dog.common.js
--dog.lib.min.js
index.js
package.json
webpack.config.js
webpack.config.js
const path = require("path");
var commonJsConfig = {
target: 'node',
mode: 'production',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'dog.common.js',
libraryTarget: 'commonjs'
}
};
var browserConfig = {
target: 'web',
mode: 'production',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'dog.lib.min.js'
}
};
module.exports = [commonJsConfig, browserConfig];
package.json (relevant parts)
main tells backend and webpack legacy projects to use the commonjs format.
module is for es6 format
unpkg for the browser
files tell npm wich files to upload
"main": "dist/dog.common.js",
"module": "index.js",
"unpkg": "dist/dog.lib.min.js",
"files": [
"dist",
"src"
],
"scripts": {
"build": "webpack"
},

Related

Can you compile a TS file with webpack separately from Vue application?

So I have a Vue 3 + Typescript app. npm run build of course takes the app and compiles it into the dist folder so it can be deployed. I have a web worker typescript file that I would like to be compiled separately so that it ends up in the root of the dist folder with the name worker.js. Here is what I'm looking for:
dist
|- worker.js
src
|- worker.ts // This gets compiled to js file in dist folder
I tried doing this by using webpack's DefinePlugin in my vue.config.js like so:
const webpack = require('webpack')
module.exports = {
configureWebpack: {
plugins: [
new webpack.DefinePlugin({
entry: `${__dirname}/src/worker.ts`,
module: {
rules: [
{
test: /worker\.ts$/,
use: 'ts-loader',
exclude: /node-modules/
}
]
},
resolve: {
extensions: ['.ts']
},
output: {
filename: 'worker.js',
path: `${__dirname}/dist`
}
})
],
resolve: {
alias: {
vue$: 'vue/dist/vue.esm-bundler.js'
}
}
}
}
Unfortunately this doesn't work, npm run build just completely ignores the worker.ts file and it doesn't show up in the dist folder anywhere, not even as a chunk. Any suggestions? Or is what I'm wanting to do even possible? Thanks for any help!
I was able to get the desired result using esbuild. Now I can write my web worker in Typescript and use classes and functions from my Vue app. It also compiles lightning fast. I just added the node ./esbuild.js command to my npm run dev and npm run build scripts, and now it compiles to the public folder before the Vue app builds. Here is my esbuild.js file.
const esbuild = require('esbuild')
const { nodeExternalsPlugin } = require('esbuild-node-externals')
const config = {
entryPoints: ['./src/worker.ts'],
outfile: 'public/worker.js',
bundle: true,
minify: false,
logLevel: 'silent',
plugins: [nodeExternalsPlugin()]
}
esbuild.build(config).catch(() => process.exit(1))
Let me know if you have any questions, I'd be happy to help anyone out getting this working.

process.env returns undefined when using .env.development instead of .env

I am using Webpack for development.
When I run the project using .env file, it works as expected.
But when I change it filename to .env.development, the process.env become undefined.
How can I fix it?
package.json
"scripts": {
"start": "webpack-dev-server --config ./webpack.config.js"
webpack.config.js
const webpack = require("webpack");
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const Dotenv = require("dotenv-webpack");
module.exports = {
mode: "development",
entry: path.resolve(__dirname, "./src/index.jsx"),
devtool: "source-map",
output: {
path: path.join(__dirname, "/dist"),
filename: "bundle.js",
},
devServer: {
port: 3000,
static: true,
historyApiFallback: true,
open: true,
},
resolve: {...},
module: {...},
plugins: [
new Dotenv(),
new HtmlWebpackPlugin({
template: "public/index.html",
filename: "index.html",
favicon: "public/favicon.ico",
}),
new webpack.ProvidePlugin({
Buffer: ["buffer", "Buffer"],
}),
new webpack.ProvidePlugin({
process: "process/browser",
}),
],
};
dotenv by default only looks for a .env file. This is deliberate, as an env file should be local only and specific for the environment the app is running in. You shouldn't be keeping around different versions of the env for different environments.
I'll quote from dotenv's docs:
Should I have multiple .env files?
No. We strongly recommend against having a "main" .env file and an
"environment" .env file like .env.test. Your config should vary
between deploys, and you should not be sharing values between
environments.
In a twelve-factor app, env vars are granular controls, each fully orthogonal to other env vars. They are never grouped together as
“environments”, but instead are independently managed for each deploy.
This is a model that scales up smoothly as the app naturally expands
into more deploys over its lifetime.
– The Twelve-Factor App
You can however, tell dotenv-webpack where to look for your file if you choose:
plugins: [
new Dotenv({
path: './.env.development',
}),

Can I have a common .env file for my app and its dependency?

I did something like this:
root/
node_modules/
myPackage/
index.js // uses the .env, can access process.env
app.js // uses the .env, can access process.env
.env
In app.js, the process object is a global, when I import myPackage the global object is also available in myPackeg/index.js. All good, hurray.
But, the node_modules/myPackage is not bundled, its just a couple of .js files with entry point at index.js. If myPackege is run through webpack build (minified, mangled) it somehow no longer is able to inherit the global process object from app.js. I don't understand why.
Webpack config of myPackage is nothing special, compiles to ES5, UMD. The code was mangled though, I excluded the 'process' from being mangled but it didn't help.
What am I missing?
webpack.config.js (without transplanting to ES5 with Babel)
module.exports = {
mode: 'production',
entry: './lib/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'myModule',
library: 'myModule',
libraryTarget: 'umd',
},
resolve: {
alias: {
lodash: path.resolve(__dirname, 'node_modules/lodash'),
'bn.js': path.resolve(__dirname, 'node_modules/bn.js'),
},
},
node: {
Buffer: false,
},
};

Webpack: How do I bundle multiple javascript files into a single output file?

Suppose I have two files, main.js and app.js; how do I use Webpack to bundle both of them into one file: bundle.js?
create one entry.js which is your webpack entry file and in this your require your additional files
webpack.config.js
module.exports = {
entry: './src/entry.js'
...
};
/src/entry.js
// new syntax
import './main.js';
import './app.js';
// or old syntax
require('./main.js');
require('./app.js');
if these two files depend on each other, it would be be beneficial to reflect this in your dependency tree and require main.js in your app.js and not in entry.js for example
You can pass an array of entries:
module.exports = {
entry: ['./main.js', './app.js'],
...
};
Also, you can pass them as a CLI option:
webpack ./main.js ./app.js --config=webpack.config.js
You can use webpack-concat-plugin.
https://www.npmjs.com/package/webpack-concat-plugin#publicpath-stringboolean-default-webpacks-publicpath
As your question is about webpack, I assume you have installed webpack already
(e.g. with yarn: yarn add --dev webpack webpack-cli).
Now you have different options:
Specify all entry files inside webpack.config.js
You can specify all wanted files inside the webpack.config.js, e.g.:
const path = require('path');
module.exports = {
entry: [
"./script1.js",
"./script2.js"
],
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
// optimization: { minimize: false },
};
Then run webpack with e.g.:
yarn webpack --config webpack.config.js
Specify all entry files inside an extra entry file
Then you can import all your .js files into one "entry" file
(like entry.js or index.js), e.g.:
// entry.js: entry file for webpack
require('./script1.js');
require('./script2.js');
And then refer to this file inside the webpack.config.js:
const path = require('path');
module.exports = {
entry: ['./entry.js'],
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
// optimization: { minimize: false },
};
Then run webpack with:
yarn webpack --config webpack.config.js
Create the entry files array dynamically
The entry property allows a function to define the entries dynamically.
E.g. in an oversimplified way:
const path = require('path');
const fs = require('fs');
const sourceFolder = './src';
const createEntryFilesArray = function(){
return new Promise(function( resolve, reject ){
fs.readdir( sourceFolder, (err, files) => {
if( !err ){
resolve(
files
.filter( file => file.endsWith('.js') )
.map( file => [ sourceFolder, file ].join('/') )
);
} else {
reject();
}
})
})
};
module.exports = {
entry: createEntryFilesArray,
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
};
But you probably want to use more solid patterns (depending on you folder structure), e.g. including the use of glob.
Furthermore, note that the file webpack.config.js is just a Javascript file,
so you could as well do whatever you want to create the entry files array dynamically.
E.g. in an oversimplified way:
const path = require('path');
const fs = require('fs');
const sourceFolder = './src';
const entryFilesArray = fs.readdirSync( sourceFolder )
.filter( file => file.endsWith('.js') )
.map( file => [ sourceFolder, file ].join('/') );
module.exports = {
entry: entryFilesArray,
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
};
Alternative: Using the TypeScript compiler
Alternatively, if you don't specifically want to use webpack,
you also could use the TypeScript compiler,
e.g. in case you just want to bundle multiple .js and / or .ts files into one .js file,
especially if you are using TypeScript anyway.
This way you wouldn't need to install Webpack.
But note that this doesn't save space if you don't need to install Typescript anyway.
The package sizes ara (currently on my machine):
typescript: 124 files, 67 MB
webpack, webpack-cli: 3.347 files, 21 MB
Bundling files with the TypeScript compiler:
Install TypeScript, e.g.:
yarn add --dev typescript
Add a tsconfig.json file, e.g.:
{
"files": [
"./script1.js",
"./script2.js"
],
"compilerOptions": {
"target": "es2018", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
"outFile": "./dist/bundle.js", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
"outDir": "./dist", /* Specify an output folder for all emitted files. */
// "removeComments": false, /* Disable emitting comments. */
// "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
"strict": true, /* Enable all strict type-checking options. */
}
}
The target folder (e.g. dist) needs to exist, it will not be automatically created.
Start the TypeScript compiler with:
yarn run tsc --project tsconfig.json
Using Plugins
Apparently there are plugins that can bundle .js files in different ways,
but unfortunately I haven't tried any plugins yet. I hope to be able to add
an example here when I know more.

Does nodejs provides options like resolve.alias in webpack

I use resolve.alias to eliminate long relative path.
// webpack.config.js
module.exports = {
// ...
resolve: {
alias: {
services: __dirname + '/src/services',
components: __dirname + '/src/components'
},
}
// componentFoo.js
import ServiceBar from 'services/serviceBar'
But when I tried using ava to run tests, node cannot find module 'services/serviceBar'.
My folder structure:
src
--components
----componentFoo.js
--services
----serviceBar.js
test
--index.js
Node.js does not have any built-in option for aliases. But you can use the babel plugin babel-plugin-module-resolver to define aliases, which should be convenient as AVA already uses babel.
You need to add it to your babel plugins:
"plugins": [
["module-resolver", {
"alias": {
"services": "./src/services",
"components": "./src/components"
}
}]
]
The paths are relative to the babel config, unless you specify the cwd option (list of options). Another possibility would be to use the root option instead of aliases, which is similar to webpack's resolve.modules:
"root": ["./src"]

Categories

Resources