dependencies, dependencies and dependencies
git clone git@github.com:sithmel/dependencies-workshop.git
Before we begin
๐ฌ๐ง
โ Time
โ Weather
๐ฎ๐น
โ Tempo
โ Tempo
Purpose:
download and make available npm packages to be consumed in Node.js
"Npm" flattens packages under node_modules
pnpm uses hardlinks and symlinks
mkdir dependencies-workshop
cd dependencies-workshop
npm init -y
npm install measure-speed
npm install memoize-cache
Check what happens to:
git checkout package
or
Package managers try to dedupe dependencies. But can also manage multiple versions
See also:
Purpose:
while executing a Node.js script. it loads the modules in memory and execute them in the correct order
โNote
Packages are resolved by path: package.json dependencies are only used for installation.
For example: nested dependencies have the precedence.โ
mkdir dependencies-workshop
cd dependencies-workshop
npm init -y
mkdir -p node_modules/dep1
cd node_modules/dep1
npm init -y
echo "module.exports = 'dep1'" > index.js
# repeat for dep2 and dep3
or
git checkout node-commonjs
Add index.js:
const dep1 = require('dep1');
const dep2 = require('dep2');
const dep3 = require('dep3');
console.log(dep1, dep2, dep3)
node index.js
Try:
Multiple versions of a dependency are supported. But:
(we will talk later about it)
Packages: https://nodejs.org/api/packages.html
// commonjs
const something = require('./something');
// ESM
import something from './something.mjs'; // this is a path or a package
// "real ESM" used by the browser
import something from './something.js'; // this is a module name or a URL
// NOTE: a browser module is the equivalent of package in node.js
Commonjs:
https://nodejs.org/api/modules.html
ESM:
https://nodejs.org/api/esm.html
and algorithm to resolve files ๐บ๏ธ
Modules: are just files in a package
Packages: a folder with a package.json that can be published
Import rules of packages and modules are determined by the package.json
https://nodejs.org/api/packages.html#nodejs-packagejson-field-definitions
and
https://docs.skypack.dev/package-authors/package-checks
{
"main": "./index-cjs.js",
"type": "module",
"exports": {
"require": "./index-cjs.js",
"import": "./index-esm.js",
"default": "./index-esm.js",
// "node", "browser", "deno", etc.
// (env-specific entrypoints, as needed)
}
}
https://nodejs.org/api/modules.html#all-together
Let's convert our repo to use ESM
git checkout node-esm
or
git checkout node-esm
or
import info from `./package.json` with { type: "json" };
Purpose:
allow js code to be bundled together in a single file
Special fields for bundlers
Same as node.js but we also have
{
"main": "./index-cjs.js",
"type": "module",
"module": "./index-esm.js", // ignored by Node.js
"sideEffects": true, // ignored by Node.js
"exports": {
"require": "./index-cjs.js",
"import": "./index-esm.js",
"default": "./index-esm.js",
// "node", "browser", "deno", etc.
// (env-specific entrypoints, as needed)
}
}
ESM can be "treeshaken":
Unimported code is removed from the bundle.
It is automatic but can be improved using sideEffects option or marking the functions as pure.
const x = */@__PURE__*/eliminated_if_not_called()
Webpack internals
Rollup internals
Vite internals
#
# dev mode
#
vite # aliases: `vite dev`, `vite serve`
#
# production mode (using rollup)
#
vite build
vite preview
npm i --save-dev rollup @rollup/plugin-node-resolve @rollup/plugin-commonjs
git checkout bundler
or
Add script to our package.json
"rollup index.js --file output.js --format iife -p @rollup/plugin-node-resolve,commonjs"
Run and look at the output
Dynamic imports
import('module').then((module) => {
// module is loaded
});
The module is created in a separated bundle (chunk). The main bundle will have a piece of code that implements the loading of the chunk.
Configure to import dynamically one of the dependencies. Some adjustment will be necessary.
Then look at the resulting bundle.
If you declare a dependency as external the bundler won't attempt to resolve it and the import will remain untouched.
Externals
Multiple versions of a dependency are supported. But:
(More about it later)
Also
Purpose:
allows to use languages other than javascript and unsupported features
Transpilation happens before bundling and execution
sourcemaps
sourcemaps
We are talking about transpilation ESM to commonjs
Transpilation from commonjs to ESM is not possible
{
"ignore": ["node_modules/**/*"],
"presets": [
["@babel/preset-typescript"],
[
"@babel/preset-env",
{
"loose": true,
"modules": false
}
],
"@babel/preset-react"
],
}
Set "modules: false" to disable transpilation from ESM to commonjs
The "module" option in tsconfig determine the output
In terms of resolving the dependencies
Typescript interoperability with commonjs is an extremely complicated topic:
https://www.typescriptlang.org/docs/handbook/modules/reference.html
in addition to the other fields we have
{
"main": "./index-cjs.js",
"type": "module",
"module": "./index-esm.js", // ignored by Node.js
"sideEffects": true, // ignored by Node.js
"types": "./index.d.ts", // ignored by Node.js and bundlers
"exports": {
"require": "./index-cjs.js",
"import": "./index-esm.js",
"default": "./index-esm.js",
// "node", "browser", "deno", etc.
// (env-specific entrypoints, as needed)
}
}
Let's convert our main module in Typescript and let's see how it works (in the context of bundling).
Note: npm install will remove your dep1/dep2/dep3 folders
npm install typescript rollup-plugin-typescript2 --save-dev
npx tsc --init
git checkout bundler-transpiled
Configure include, module and moduleResolution in tsconfig.json.
This is the new script
or
rollup index.ts --file output.js --format iife -p @rollup/plugin-node-resolve,commonjs,typescript2
๐ฅ They are managed up to a point but tricky
๐ฆ Package dependencies
depend on SEMVER and deduped when possible
https://semver.org/
Package1
"my_dep": "^1.0.0" ๐ ok: 1.0.3 - 1.1.2 ko: 2.0.0
"another_dep": "~2.2.0" ๐ ok: 2.2.3 ko: 2.2.1
Package2
"my_dep": "^1.2.5" ๐ ok: 1.3.4 - 1.2.6 ko: 2.0.0
"another_dep": "~2.3.0" ๐ ok: 2.3.1 ko: 2.4.0
Result (Package1 + Package2)
"my_dep": "1.2.5"
"another_dep": "2.2.0"
"another_dep": "2.3.0"
โ ๏ธ Duplicated!
๐ฅ๏ธ Node.js dependencies
and
๐ฅก Bundle dependencies
They depend on the folder position
// every lib version overwrites the previous one
window.something = something
// every lib version ADD a new event handler
document.body.addEventListener('click', doSomething);
// every lib version repeat the same initialisation
fetch("https://something.com/specialconfig").then(dosomething)
// this object is generated by version 1 of a package
const data = {
result: "here is the result"
}
// this is a function in the version 2 of the same package
function printData(data) {
console(data.output); // error!
}
Duplicated dependencies will import an entire new dependency tree!
ESM full documentation:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules
ESM works natively in the browser while commonjs is not supported
<script type="module" src="main.js"></script>
<script type="module">
/* JavaScript module code here */
</script>
Only allowed in modules
<script type="importmap">
{
"imports": {
"square": "./shapes/square.js"
}
}
</script>
import { name, draw, reportArea, reportPerimeter } from "square";
Support urls or bare names
import("./modules/myModule.js").then((module) => {
// Do something with the module.
});
Support dynamic loading (using promises)
๐ฌ๐ง dependencies
๐ฆ Package dependencies
๐ฅ๏ธ Node.js dependencies
๐ฅก Bundle dependencies
โป๏ธ Transpiled dependencies
๐ Browser dependencies
๐คฏ
the end? ๐