Webpack tree shaking with unusued import - node.js

I am trying to develop a library similar to Axios for both client and server side.
I was hoping that the tree shaking feature from webpack would help me to remove dead code from imports when used in client side, but it doesn't. Here is a short example of the code:
import http from "http";
import https from "https";
export class FetchClient {
static request(...) { ... }
};
export class FetchServer {
static request(...) { ... }
}
When I only import FetchClient, webpack still includes the code for "http" and "https" in my bundle, which makes the bundle ~100KB larger than expected, eventhough it is not used in this code path.
Also I looked at the code for Axios, it seems they are using conditional require instead of import (see the adapter code part). Would the trick be to load conditionaly and therfore asynchronously the http and https dependencies? In that case the code would still be in my bundle, which is somethign I would like to avoid.
Note: I am using the TerserPlugin (minimizer), "sideEffects": false in my packages.json and modules all the way.
EDIT:
I managed to go around my issue, by using the EnvironmentPlugin webpack plugin and splitting my code in 3 files: 2 files containing each implementation (for client and server) and the main file containing the following logic:
let adapter = null;
if (process.env.EXEC_ENV === "node") {
adapter = (await import("./adapter/node.http.js")).default;
}
else if (process.env.EXEC_ENV === "web") {
adapter = (await import(/* webpackMode: "eager" */"./adapter/window.fetch.js")).default;
}
I find it quite strange that webpack is not able to do this by himself, I thought that's what the sideEffects flag was for. Maybe the static analyzer is not smart enough?

import https from "https";
Importing like this will import all the files. Since it does not know, there might be some global initialization. Same time HTTP, and HTTPS module will not be available on client-side. So it has to be browsify.
Axios also include follow-redirects, which includes both HTTP and HTTPS module.
Axios:
transport = isHttpsProxy ? httpsFollow : httpFollow;
This is not condition require, this is a conditional return. adaptor pattern.

Related

(webpack) How to import webpack bundled modules as one instance in all require

I am implementing server-side rendering using react and express. So, I am using the context of react in Express.
However, the problem is that every time the context below is requeried, a new context is created and the context cannot be shared by other modules.
import { createContext, ReactElement } from "react";
type State = {
main: ReactElement | null;
srcList: Array<string> | null;
};
export default class Context {
static HtmlContext = createContext<State>({
main: null,
srcList: [],
});
}
What I wanna know is how to import webpack bundled modules as one instance in all require.
Similar to the singleton pattern, I was trying to find a webpack configuration that allows only one instance to be shared. And I was trying to use optimization option with runtimeChunk: 'single'. But it didn't work well and I don't know if I understood it well...
I would be very grateful if you could learn how to approach it.

Webpack / Vue.js: generate module code at compile-time using ESM dependencies

Environment: webpack 5.44 + vue.js 3.0 + node 12.21
I'm trying to generate a module at compile-time, in order to avoid a costly computation at run-time (as well as 10Mb of dependencies that will never be used except during said computation). Basically run this at compile-time:
import * as BigModule from "big-module";
function extract_info(module) { ... }
export default extract_info(BigModule);
which will be imported at run-time as:
export default [ /* static info */ ];
I tried using val-loader (latest 4.0) which seems designed exactly for this use case.
Problem: big-module is an ESM, but val-loader apparently only supports CJS. So I can neither import ("Cannot use import statement outside a module" error) nor require ("Unexpected token 'export'" error).
Is there any way to make val-loader somehow load the ESM module? Note that I'm not bent on using val-loader, any other technique that achieves the same goal is just as welcome.
After learning way more than I wanted about this issue and node/webpack internals, there seems to be two possible approaches to import ESM from CJS:
Use dynamic import(). But it is asynchronous which makes it unfit here, as val-loader requires a synchronous result.
Transpile the ESM into CJS, which is the approach I took.
In my case, full transpiling is overkill and rewriting imports/exports is sufficient, so I'm using ascjs to rewrite the ESM files, along with eval to safely evaluate the resulting string.
All in all:
// require-esm.js
const fs = require('fs');
const ascjs = require('ascjs');
const _eval = require('eval');
function requireESM(file) {
file = require.resolve(file);
return _eval(ascjs(fs.readFileSync(file)), file, { require: requireESM }, true);
}
module.exports = requireESM;
// val-loader-target.js
const requireESM = require('./require-esm');
const BigModule = requireESM('big-module');
function extract_info(module) { ... }
module.exports = extract_info(BigModule);
Note that:
ascjs is safe to use on CJS modules, since it only rewrites ESM imports/exports. So it's OK for big-module or its dependencies to require CJS files.
the third argument to _eval enables recursive rewriting, otherwise only the top-level file (the one passed to requireESM) is translated.

Put data on ServersideProps

Is it possible to get data from server side's config file on client by getServersideProps() in Next.js? How to put data in props? Or how to get it on client in other way?
I have tried publicRuntimeConfig in next.config.js, but it is undefined and I don't know why...
It's hard to tell exactly what's going on but I have one idea from experience: You need to make sure you're calling nextJSApp.prepare() before any modules using next/config are included.
As an example,
// SomeComponent.tsx
import { getConfig } from 'next/config'
const config = getConfig()
export interface X { ... }
// server.ts
import { X } from './SomeComponent'
app.prepare().then(...)
This fails because module are loaded first and the config hasn't been initialized until app.prepare has been completed.
The solution for this is to either use TypeScript's import(...) syntax if you just need a type or use node's require for dynamic resolution during runtime.

Jest Mock Globally From a Node Module

I am writing a series of Node modules that require a bunch of common Jest mocks to be setup before I can write unit tests.
I am trying to refactor the unit test setup into a separate module so I can avoid rewriting the setup each time.
The problem is that when I import the following code as a module it no longer mocks the other libraries, while it works just fine when the mocks are set up in a utility file.
Working Code:
jest.mock('#actions/core')
jest.mock('#actions/github')
const { GitHub, context} = require('#actions/github')
const core = require('#actions/core')
GitHub.mockImplementation(() => {
return {
{
repos: {
getContents: jest.fn()
}
}
}
}
module.exports = { core, GitHub, context }
I keep this in a utils.js file next to my test files and import it like const { core, GitHub, context } = require('./utils.js') and everything mocks as I expect. I can run expect().toHaveBeenCalledTimes() and get the numbers I expect.
The problem appears when I move utils.js to another module and require it.
I know that at bottom of the jest documentation it says "...Another file that imports the module will get the original implementation even if it runs after the test file that mocks the module." But I am seeing this work inconsistently with the external file.
Anyone know how to get mocking setup in external modules?

How can I avoid always having to import my own code in Typescript?

So I was recently hacking on a large Typescript project (https://github.com/BabylonJS/Babylon.js) and I noticed that they don't ever have to import anything, they just use their namespace and the rest is (seemingly) magic.
It got me thinking that I would like to use something similar for myself, so I started a simple typescript project to try it out.
tsconfig.json
{
"compilerOptions": {
"baseUrl": "src",
"outFile": "server.js"
}
}
src/main.ts
module Test {
console.log('Main started')
const server:Server = new Server()
}
src/Server.ts
// import * as http from 'http'
module Test {
export class Server {
constructor() {
console.log('Server initialized')
}
}
}
If I build this Typescript project then I get output like the following:
// import * as http from 'http'
var Test;
(function (Test) {
var Server = /** #class */ (function () {
function Server() {
console.log('Server initialized');
}
return Server;
}());
Test.Server = Server;
})(Test || (Test = {}));
var Test;
(function (Test) {
console.log('Main started');
var server = new Test.Server();
})(Test || (Test = {}));
So far, so good. The trouble is that I want to take advantage of some external modules, in this case namely the http module, so I uncomment the import line above and now Typescript reports:
src/server/Server.ts(1,1): error TS6131: Cannot compile modules using option 'outFile' unless the '--module' flag is 'amd' or 'system'.
Node uses the commonjs module system, so obviously setting either of those flags isn't going to help me much. I have none the less tried them as well as various other combinations of flags, but to no avail. I noticed that BabylonJS doesn't really use external imports like that, opting instead to declare them as external and provide them globally at execution time as script tags. Is there maybe an analogue to that for Node?
You can't have these two things at the same time, namely
How can I avoid always having to import my own code in Typescript?
and
I want to take advantage of some external modules
You can avoid imports only by not using external modules, and the result will be one giant script file that can use external dependencies only as globals created by scripts loaded via script tag, as you already noticed.
The language does not allow you to use external modules when you do this. If you have an import of external module at the top level, your file becomes a module and there is no way it could use code from your other files without importing them. And having import of external module inside a namespace is not allowed AFAIK.
That said, I don't think your question - "How can I avoid always having to import my own code in Typescript?" - has a valid premise. CommonJS module system is the solution for preventing large projects from becoming unmaintainable mess. It does not matter if some part of a project is your own code or some external dependency - if it's a separate part with well-defined interface, it should be packaged and consumed as a module.
The solution that worked for me is this:
Server.ts
declare var http: any;
namespace Test {
export class Server {
constructor() {
console.log('Server initialized')
const server = http.createServer()
server.listen()
}
}
}
Then I simply provide http at runtime, for example by prepending a var http = require('http') to the output. Thanks to artem for a nudge in the right direction.

Resources