Node.js lazy require module - node.js

I'm writing a lazy module require() or import
const lazy = new Proxy({},
{
get: function (target, name) {
console.log('lazy require', { target, name })
return require(name)
}
}
)
/**
* #param {string} Module Name
* #example const expo = requirez('expo')
*/
export default function requirez(name) {
return lazy[name]
}
and oddly when I run it I get:
Cannot find module "."
The console.log statement logs:
lazy require {target: {…}, name: "./Linking"}
So require(name) should be getting called as: require("./Linking")
Not as require(".") which the error is indicating.

Found a related bug report:
https://github.com/webpack/webpack/issues/4921
Since node require tree resolution is evaluated/analyzed statically and webpack assumes this it fails on dynamic resolution.
Also, on browser webpack will have to transpile the required bundle before running in the browser thus dynamic lazy requires can't be run after transpilation. You'd be missing the transpiled source of that required module.
I've tried using import() but it also has bugs:
https://github.com/webpack/webpack/issues/4292#issuecomment-280165950

Related

How can I use packages that extend `koa.Request` in TypeScript?

I am trying to use koa-tree-router and koa-bodyparser at the same time, but I keep getting TypeScript errors:
export const userLoggingRouter = new KoaTreeRouter<any, DefaultContext>();
userLoggingRouter.post('/logs/action', (ctx) => {
const logEntries = ctx.request.body;
const user = ctx.state.user;
// ...
});
error TS2339: Property 'body' does not exist on type 'Request'.
I have #types/koa-bodyparser installed, and it contains the following definition:
import * as Koa from 'koa';
declare module 'koa' {
interface Request {
body: string | Record<string, unknown>;
rawBody: string;
}
}
But it doesn't seem to do anything. I found this question, but importing koa-bodyparser directly also does not do anything. How do I get TypeScript to recognize the extended Request type?
Edit: Creating a .d.ts file inside my project containing the following:
import {Request} from "koa";
declare module "koa" {
interface Request {
body: any;
}
}
Made the compile error go away, but this seems like an inelegant solution because I would have to copy over type information for every package that modifies koa.Request.
This was happening because I was using Yarn PnP and I had two different versions of #types/koa installed. Once I added a resolutions field to my package.json that forced all of the other TypeScript definitions to use the same version of #types/koa, everything worked.

Transforming UMD modules to ES modules in RollupJS ("The requested module X does not provide an export named 'default'")

I am currently having a TypeScript project that includes Ethers.js, which in turn includes bn.js.
The problem is
SyntaxError: The requested module './../../../bn.js/lib/bn.js' does not provide an export named 'default'
It appears to me this is because BN is in UMD format (see at https://github.com/indutny/bn.js/blob/master/lib/bn.js#L1)
(function (module, exports) {
'use strict';
// Utils
function assert (val, msg) {
if (!val) throw new Error(msg || 'Assertion failed');
}
and the correponding .ts declaration is
"use strict";
/**
* BigNumber
*
* A wrapper around the BN.js object. We use the BN.js library
* because it is used by elliptic, so it is required regardless.
*
*/
import _BN from "bn.js";
import BN = _BN.BN;
import { Bytes, Hexable, hexlify, isBytes, isHexString } from "#ethersproject/bytes";
import { Logger } from "#ethersproject/logger";
import { version } from "./_version";
const logger = new Logger(version);
It may be there's something that could be done at importing (source) or in Rollup. Difficult to tell!
Here is a screenshot of the build errors (one variation, depending on if building or running directly)
Question: Is there a way to transform this format to an ESM format in application Rollup pipeline?
I have tried using #rollup/plugin-commonjs and #rollup/plugin-node-resolve as in
resolve({ browser: true, preferBuiltins: false }), commonjs()]
(or see the project as whole at https://github.com/veikkoeeva/erc1155sample/blob/main/web/rollup.config.js, the error shows with npm run test or npm run start (in console log)).
Thus far I've had no luck cracking this, though. Hence coming here wondering if there's a dumb issue I don't see or if this is a genuinely tougher issue.
Edit: indeed, following https://rollupjs.org/guide/en/#error-name-is-not-exported-by-module and maybe named exports is the key here...

Node repl module not found

I am trying to write a simple node script with some classes.
I first define a class
export default class Checkout() {
constructor () {
console.log('checkout')
}
check() {
console.log('check')
}
}
Then I am trying to use it
>node
>repl
check = new Checkout()
Uncaught ReferenceError: Checkout is not defined
require('Checkout')
Uncaught Error: Cannot find module 'Checkout'
How can I solve this? I am coming from Ruby where the console is pretty straight forward.
Code in a file isn't included in any other scope outside of that file until you require or import the file. When requiring/importing, the name the object you exported from the file doesn't automatically get used, you still have to specify it.
However, you're currently mixing require (CommonJS module format) with export default (ECMAScript Module format). There is only very limited interoperability between these formats using dynamic import(), but it's not yet available in the Node REPL (open issue here). If you need to test your Checkout class in the REPL, you'd need to just switch to using CommonJS:
module.exports = class Checkout() {
constructor () {
console.log('checkout')
}
check() {
console.log('check')
}
}
Usage:
> Checkout = require('checkout.js')
> check = new Checkout()

Gulp + Browserify + TypeScript To Browser

My problem is the following:
I use gulp+browserify to compile my TypeScript to JavaScript that you can use on normal HTML pages, the problem is that my class is never available on the browser:
VM633:1 Uncaught ReferenceError: Test is not defined
at <anonymous>:1:13
This is my TypeScript File:
class Test {
public test(): void {
console.log("aa");
}
}
This is my gulpfile
var gulp = require("gulp");
var browserify = require("browserify");
var source = require('vinyl-source-stream');
var tsify = require("tsify");
gulp.task("default", function () {
return browserify({
//basedir: '.',
debug: true,
entries: ['app/Resources/typescript/Test.ts'],
cache: {},
packageCache: {}
})
.plugin(tsify)
.bundle()
.pipe(source('bundle.js'))
.pipe(gulp.dest("web/bundles/framework/js"));
});
The file compiles without problem, and is included in my index.html (the compiled js file).
But when i try:
var t = new Test();
I get the following error:
VM633:1 Uncaught ReferenceError: Test is not defined
at <anonymous>:1:13
I can't resolve it, I have read a lot and I haven't found anything clear, I tried all and nothing worked.
There are a few things missing here:
If you want your class to be accessible outside of your module, you have to export it:
export class Test {
// ...
}
Browserify creates functions on top of the classes you define. So it won't be accessible globally (which is a good thing). Normally you would import it in another file and use it:
// in any TS file that wants to use `Test` class. Make sure this is included in the gulp entries as well
import {Test} from "test";
var t = new Test();
console.log(t);
Or if really want it to be accessible globally, you can attach it to window object:
// In Test.ts file:
(window as any).Test = Test; // This can be useful when debuging. But don't do this in production code.

What is the correct way to load polyfills and shims with Browserify

I'm building a web app and I'm getting to know and love Browserify. One thing has bugged me though.
I'm using some ES6 features that need to be shimmed/polyfilled in older browsers such as es6-promise and object-assign (packages on npm).
Currently I'm just loading them into each module that needs them:
var assign = require('object-assign');
var Promise = require('es6-promise');
I know this is definitely not the way to go. It is hard to maintain and I would like to use the ES6 features transparently instead of having to "depend" on them via requires.
What's the definitive way to load shims like these? I've seen several examples around the internet but they're all different. I could:
load them externally:
var bundle = browserify();
bundle.require('s6-promise');
// or should I use it bundle.add to make sure the code is runned???
The problem I have here is that I don't know which order the modules
will be loaded in in the browser. So the polyfilling might not have happened
yet at call sites that need the polyfilled functionality.
This has the additional downside that backend code cannot benefit from these
polyfills (unless I'm missing something).
use browserify-shim or something similar. I don't really see how this would work for ES6 features.
manually set up the polyfilling:
Object.assign = require('object-assign');
Don't require polyfills in your modules, that's an anti-pattern. Your modules should assume that the runtime is patched (when needed), and that should be part of the contract. A good example of this is ReactJS, where they explicitly define the minimum requirement for the runtime so that the library can work: http://facebook.github.io/react/docs/working-with-the-browser.html#browser-support-and-polyfills
You could use a polyfill service (e.g.: https://cdn.polyfill.io/) to include an optimized script tag at the top of your page to guarantee that the runtime is patched correctly with the pieces you need, while modern browsers will not get penalized.
This is the method that I'm using. The key is that you have to export your polyfill properly at the top of your main entry file.
The following won't work:
// Using ES6 imports
import './polyfill';
// Using CommonJS style
require('./polyfill');
... // rest of your code goes here
You actually need to export the polyfill:
// Using ES6 export
export * from './polyfill';
// Using CommonJS style
var polyfill = require('./polyfill');
... // rest of your code goes here
Your polyfills will load correctly if you do either of the latter methods.
Below you can find examples of my polyfills.
polyfill.js:
import './polyfill/Array.from';
import './polyfill/Object.assign';
Object.assign:
if (typeof Object.assign !== 'function') {
(function iife() {
const ObjectHasOwnProperty = Object.prototype.hasOwnProperty;
/**
* Copy the values of all enumerable own properties from one source
* object to a target object. It will return the target object.
* #param {Object} target The target object.
* #param {Object} source The source object.
* #return {Object} The target object.
*/
function shallowAssign(target, source) {
if (target === source) return target;
Object.keys(source).forEach((key) => {
// Avoid bugs when hasOwnProperty is shadowed
if (ObjectHasOwnProperty.call(source, key)) {
target[key] = source[key];
}
});
return target;
}
/**
* Copy the values of all enumerable own properties from one source
* object to a target object. It will return the target object.
* #param {Object} target The target object.
* #param {Object} source The source object.
* #return {Object} The target object.
*/
Object.assign = function assign(target, ...sources) {
if (target === null || target === undefined) {
throw new TypeError('Cannot convert undefined or null to object');
}
sources.forEach((source) => {
if (source !== null) { // Skip over if undefined or null
shallowAssign(Object(target), Object(source));
}
});
return target;
};
}());
}
One solution that worked for me was to use bundle.add
I split my bundle in 2 parts, app.js for app code, and appLib.js for libraries (this one will be cached as it does not change oftenly).
See https://github.com/sogko/gulp-recipes/tree/master/browserify-separating-app-and-vendor-bundles
For appLibs.js I use bundle.add for polyfills, as they must be loaded when the script is loaded, while I use bundle.require for other libs, that will be loaded only when required inside app.js.
polyfills.forEach(function(polyfill) {
b.add(polyfill);
});
libs.forEach(function(lib) {
b.require(lib);
});
The page loads these 2 bundles in order:
<head>
...
<script type="text/javascript" src="appLibs.js" crossorigin></script>
<script type="text/javascript" src="app.js" crossorigin></script>
...
</head>
This way it seems safe to assume that all polyfills will be loaded even before other libs are initialized. Not sure it's the best option but it worked for me.
My complete setup:
"use strict";
var browserify = require('browserify');
var gulp = require('gulp');
var gutil = require('gulp-util');
var handleErrors = require('../util/handleErrors');
var source = require('vinyl-source-stream');
var watchify = require("watchify");
var livereload = require('gulp-livereload');
var gulpif = require("gulp-if");
var buffer = require('vinyl-buffer');
var uglify = require('gulp-uglify');
// polyfills should be automatically loaded, even if they are never required
var polyfills = [
"intl"
];
var libs = [
"ajax-interceptor",
"autolinker",
"bounded-cache",
"fuse.js",
"highlight.js",
"imagesloaded",
"iscroll",
"jquery",
"keymaster",
"lodash",
"medium-editor",
"mime-db",
"mime-types",
"moment",
"packery",
"q",
"rangy",
"spin.js",
"steady",
"store",
"string",
"uuid",
"react-dnd"
];
// permits to create a special bundle for vendor libs
// See https://github.com/sogko/gulp-recipes/tree/master/browserify-separating-app-and-vendor-bundles
gulp.task('browserify-libs', function () {
var b = browserify({
debug: true
});
polyfills.forEach(function(polyfill) {
b.add(polyfill);
});
libs.forEach(function(lib) {
b.require(lib);
});
return b.bundle()
.on('error', handleErrors)
.pipe(source('appLibs.js'))
// TODO use node_env instead of "global.buildNoWatch"
.pipe(gulpif(global.buildNoWatch, buffer()))
.pipe(gulpif(global.buildNoWatch, uglify()))
.pipe(gulp.dest('./build'));
});
// Inspired by http://truongtx.me/2014/08/06/using-watchify-with-gulp-for-fast-browserify-build/
gulp.task('browserify',['cleanAppJs','browserify-libs'],function browserifyShare(){
var b = browserify({
cache: {},
packageCache: {},
fullPaths: true,
extensions: ['.jsx'],
paths: ['./node_modules','./src/'],
debug: true
});
b.transform('reactify');
libs.forEach(function(lib) {
b.external(lib);
});
// TODO use node_env instead of "global.buildNoWatch"
if ( !global.buildNoWatch ) {
b = watchify(b);
b.on('update', function() {
gutil.log("Watchify detected change -> Rebuilding bundle");
return bundleShare(b);
});
}
b.on('error', handleErrors);
//b.add('app.js'); // It seems to produce weird behaviors when both using "add" and "require"
// expose does not seem to work well... see https://github.com/substack/node-browserify/issues/850
b.require('app.js',{expose: 'app'});
return bundleShare(b);
});
function bundleShare(b) {
return b.bundle()
.on('error', handleErrors)
.pipe(source('app.js'))
.pipe(gulp.dest('./build'))
// TODO use node_env instead of "global.buildNoWatch"
.pipe(gulpif(!global.buildNoWatch, livereload()));
}
As you can see
Or use the polyfill service at
https://cdn.polyfill.io/v2/docs/

Resources