Expanding JavaScript `require` statement - javascript

In a folder, I have the following JavaScript file main.js:
const o = require("./other.js");
console.log(o.f());
and the following JavaScript file other.js:
function f() { return 1.23; }
exports.f = f;
I need to have an equivalent file with no dependencies on other files, like the following JavaScript file bundle.js:
function f() { return 1.23; }
console.log(f());
How can I do that?
I tried to use the Rollup Nodejs extension, with this command:
rollup main.js -o bundle.js -f cjs
Though, the require statement remained in the bundle.js file, and if I remove the other.js, it is not working anymore.

You're correct - what you're looking for is a bundler. Rollup, however, will require specific configuration to work with your use-case; it doesn't support CommonJS imports (require) by default and will, as you've seen, leave them as-is.
If you'd like to use Rollup, you can resolve this by creating a configuration file that will look something like this:
import commonjs from '#rollup/plugin-commonjs';
export default {
input: 'main.js',
output: {
dir: 'output',
format: 'cjs'
},
plugins: [commonjs()]
};
and passing it to rollup --config bla.js.
You could also use a different bundler, such as esbuild; running npx esbuild main.js --bundle --outfile=out.js will do exactly what you're looking for. There are various advantages and disadvantages to using both - esbuild is extremely fast but is considered less mature than other bundlers.

Related

How to split a library and its plugins among npm packages?

Im working on a JavaScript library and I want future users to be able to pick and choose the plugins they want to add to their project among with the main library. I'm having few issues with modules and webpack. I'm writing pseudo code to give an idea of how the code is organized.
My index.js for the main library looks like this:
import ClassA from "./classA";
import ClassB from "./classB";
export default class MyLib {
.....
}
export { ClassA, ClassB }
I can easily output the library with webpack:
output: {
path: ...
filename: 'mylib.min.js',
library: "MyLib",
libraryTarget: "umd"
}
To be able to choose which plugins to add, I'm creating different npm packages (one for each plugin), adding MyLib as an external dependency and then doing:
import {ClassA, ClassB} from "MyLib";
class PluginA extends ClassB {
constructor() {
this.test = new ClassA();
}
}
This works perfectly but, when "compiling" PluginA, webpack would include MyLib in the final js file for PluginA. If I want to include multiple plugins the code would end up with multiple copies of the main lib.
My final goal is to organize the code in such a way that can be easily installed with the following npm commands without having duplicated code everywhere:
npm install MyLib
npm install MyLib-PluginA
npm install MyLib-PluginB
Of course, one obvious solution would be to not use webpack for the plugins but I'd like to keep this option as the last resource in case nothing else works.
Thanks!
I wouldn't recommend using webpack to build your plugins/library. Rather, I'd let the consumer of the library decide on their own bundler. Your best step for the library should just be transpilation (if needed) of any intermediate code like babel-featured JS or TypeScript into something that can be safely require'd by node.
In addition, each plugin ought to have MyLib as a peerDependency instead of a regular dependency. That will make sure that MyLib doesn't get nested inside of the plugin's node_modules and will thus avoid duplicates being bundled. The plugins could in addition has MyLib as a devDependency for the sake of unit tests, but the important bit is that it's never a regular dependency.
Digging into webpack documentation, I've found a solution that uses webpack's externals.
From webpack documentation:
The externals configuration option provides a way of excluding dependencies from the output bundles.
I've just added the following lines to the webpack's configuration for the plugin:
module.exports = {
...,
externals: {
mylib: {
commonjs: 'MyLib',
commonjs2: 'MyLib',
amd: 'MyLib',
root: 'MyLib'
}
}
};
Webpack documentation: https://webpack.js.org/configuration/externals/
Hope this will help others.

Run tests against compiled bundles

As a JS library author I compile and publish my source code in the following formats:
commonJS (/lib/index.js)
ES (/es/index.js)
UMD (/dist/index.js)
My unit tests cover my source code and I trust my bundling/compiling tools to generate working bundles.
Then I read this enlightening post by React team where they explain that they run part of their unit tests against the bundled version of the library.
They introduced a test-build-prod which runs Jest with a special configuration file which replace the original import declarations in the test to point the bundled files using Jest's moduleNameMapper option.
It's cool but a bit overwhelming to replicate in my tiny open source side projects.
Before I give this a try, is there any other tool or more portable solution I should consider to run the same test I run on my source code against the compiled bundles?
I'll share the solution I finally went with, which is the same adopted by React team but on small scale.
I added a special npm script to run my unit tests against each compiled bundle with a different Jest configuration for each bundle:
{
"test:bundles": "jest --config ./jest/es.config.js && jest --config ./jest/lib.config.js && jest --config ./jest/dist.config.js"
}
Each Jest configuration file extends default Jest configuration and declares a moduleNameMapper object plus a rootDir property like:
// jest/lib.config.js
const pkg = require('../package.json');
module.exports = Object.assign({}, pkg.jest, {
rootDir: '../',
moduleNameMapper: {
'/src/index$': '<rootDir>/lib/index', // path of "CommonJS" bundle
},
});
moduleNameMapper will make sure that the same tests used for source code will run against an exported bundle since Jest will transform the import statements on runtime like:
import myLibrary from './src/index';
// transformed into:
import myLibrary from '../lib/index';
The only point to pay attention to is making tests import statements needing remapping easily recognizable by moduleNameMapper regex.
If import from './index' is not unique enough in test the files, it might be rewritten as import from '../src/index'.

Typescript Modules Into Single JS file?

Ok, I am complete lost with this. I have just started using Typescript with Grunt JS and need some help.
I have a working Grunt file thats runs my TS and then a uglify process for site ready files, here is the config:
ts: {
default: {
files: {
'/js/builds/main.js': ['/typescript/main/build.ts'],
'/js/builds/public.js': ['/typescript/public/build.ts']
}
},
options: {
target: 'ES5',
fast: 'never',
sourceMap: false,
allowJs: true,
declaration: false,
module: 'amd'
},
},
'uglify': {
options: {
preserveComments: 'some',
},
my_target: {
files: {
'src/js/main.js': ['/js/builds/main.js'],
'src/js/public.js': ['/js/builds/public.js']
}
}
},
watch: {
'JS': {
files: [
'/js/**/*.js',
'/typescript/**/*.ts',
'/typescript/**/**/*.ts'
],
tasks: ['ts', 'uglify'],
options: {
spawn: false,
},
}
}
So now I am working on my Typescript files, but I having a lot of issues, I want to use Typescript as a module system but to output into a single file, right now its set to AMD, which needs Require JS of which I dont know.
Right now I don't have the time to learn Typescript and Require JS. So where is what I have got right now,
test.js:
export class testMe {
constructor() { }
LogingData() {
console.log( 'Data being logged...' );
}
}
Then in my build ts file,
import {testMe} from "./clients/clients";
However this needs Require JS or module loading system in order to get it to run? I have tried using commonJs but it seems support for that was removed in Typescript 1.8 (I am using 2.0).
So how do I get my Grunt / Typescript into a single standard ES5 complied code, while using modules for Typescript?
Many thanks
UPDATE
This question & answer, Typescript compile to single file does not give an answer for how to setup grunt to use Typescript into a single file! Plus the answer states that Typescript 1.8+ will fix that issue - But the version I am using does not.
I am using Grunt to compile the TS code into one standard javascript file, without the use of System or Require JS. So I can use that within my HTML code.
The end goal would be to have two single files. To explain I have lots of single .ts files for two sections, one main and the other public - I have not work on the public section, just the main one, so all my tests I been on that section.
So to layout my file/folder path:
js/
builds/
main.js < targer end file
public.js <- target end file
typescript
main/
settings/
classA.ts
somethingelse.ts
othersection/
something.ts
buildMain.ts <- *1
*1 This file then takes all the ts files, via imports (not sure if thats the correct way) and then builds the complete standard single .js file.
I hope that explains my query in more detail.
Thanks
UPDATE 2:
I would just like to add that I am getting a single file, e.g. main.js but that is not a standard Javascript complied file. I don't want to use Require JS or any other external module loading system.
I want to use external .ts files has modules import them into a 'build' file and have that file compile down to a standard self contained javascript file without the need to include require js or anything else.
Hope that clears it up a little more..
Thanks.
I believe you can use --outfile to get everything into one file. The trick is to remove import and export statements and use /// <reference path="path/to/file.ts" /> on the first lines to declare the dependency / ordering relationships. Since it is the import/export statements that produce the calls to CommonJS or Require, omitting them prevents their generation.
Ref: https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html
For two output files, you'll probably need two tsconfig.json. (I might be incorrect here.)
If you don't bother with .d.ts files you can simply use just --module=commonjs(it is and will be supported) to compile each file into .js and then browserify to concat all modules together.
I'm using CLI scripts instead of grunt but it should be obvious what it does:
$ ./node_modules/.bin/tsc --module commonjs main.ts
$ ./node_modules/.bin/browserify main.js -o bundle.js
Then you can run it like any other JavaScript from browser or CLI using node.
There's also --outFile option for tsc but this is limited only to amd and systemjs modules so I think it's easier to stick to commonjs and browserify.
browserify is an amazing tool. You can generate UMD module with --standalone parameter that works also as global or exclude any external dependencies.
I highly recommend you to read the Official Handbook: https://github.com/substack/browserify-handbook

Trouble using babel transpiler with WebPack

I'm trying to use this package in my application.
It appears to be written in ES6 so I need a transpiler like babel. I've started a new project and tried the following:
Create new index .html / .js file for testing.
npm install audio-effects
npm install gulp gulp-babel babel-preset-es2015
Create .babelrc
After trying to run this from the dist folder with python -m SimpleHTTPServer, I got an error: index.js:3 Uncaught ReferenceError: require is not defined.
After some digging, this is because require can't be used client-side. So next I looked into using WebPack to allow me to use require.
I went into my dist folder (where my transpiled javascript is) and ran:
webpack ./index.js index.js
But now I'm getting the error index.js:78 Uncaught SyntaxError: Unexpected token import.
Can anybody see what I'm missing (apart from a NPM-ES6-Gulp-WebPack tutorial)? I seem to be stuck in a loop of WebPack-ing and transpiling.
index.html
<!DOCTYPE html>
<html>
<body>
<h4>Welcome</h4>
<button onclick="startAudio()">Start Audio</button>
<script src="js/index.js"></script>
<script type="text/javascript" src="bundle.js" charset="utf-8"></script>
</body>
</html>
index.js (pre-babel / WebPack - ification)
import {HasAudioContext} from 'audio-effects';
function startAudio() {
console.log("Start Audio...");
let audioContext = null;
if (HasAudioContext) {
console.log("Has Audio CTX");
audioContext = new AudioContext();
}
else {
console.log("No Audio CTX");
}
}
gulpfile.js
var gulp = require("gulp");
var babel = require("gulp-babel");
gulp.task("default", function () {
return gulp.src("src/app.js")
.pipe(babel())
.pipe(gulp.dest("dist"));
});
I've made some changes to the library (I'm the original author of the package). When installing the package with npm, you will now get the transpiled ES5 code instead of the ES6 source. You'll still need webpack, browserify, ... to load the module though.
This might fix the the Uncaught SyntaxError: Unexpected token import error, so please update your audio-effects library to the latest version.
The wrong imports as mentioned in the answer by Jorawar Singh should be resolved as well.
I'm still working on the library so if you run into any problems, feel free to create an issue or pull request on github.
I personally use the package with webpack. this is my webpack.config.babel.js file (remove comments).
Note: I'm using react, if you don't set the react parameter to false.
import config from 'madewithlove-webpack-config';
export default config({
react: true,
sourcePath: 'src', // Source directory
outputPath: 'builds', // Transpiled coded directory
});
This imports a basic webpack config from https://github.com/madewithlove/webpack-config/
Since I'm writing code in ES6, I'm transpiling it with babel, my .babelrc file looks like this:
{
"presets": ["es2015", "stage-0"],
}
With all this setup, you can just run webpack with webpack-dev-server --inline --hot.
You don't have to use the madewitlove webpack config but it takes care of some standard setup like compiling scss etc.
I hope this gives you an insight in how to use the audio-effects package or any other ES6 package.
Well what i understand there was some issues with this library it was written es6 and when you do import and want to complie into es5 with webpack then webpack will also bummbel all the require modules for you. Here's my webpack.config look likes
var webpack = require('webpack');
var config = {
entry: './index.js',
output: {
path: __dirname + '/dist',
filename: 'bundle.js'
},
module: {
loaders: [ {
test: /\.js$/,
loader: 'babel-loader',
query: {
presets: ['es2015']
}
}]
}
};
module.exports = config;
running by webpack will compile the library and your index.js file to bundle.js
there was some other issues i think in order to get this library you need to do some small changes in library.
from
'./Helpers/HasAudioContext'; //this doesn't exist and
//webpack will give you compile error
to
'./helpers/hasAudioContext';
I had one issue whitch i couldn't resolve is i couldn't run the startAudio function but through the index.js i could (weard let me know if you find why)
in your index.js
document.getElementById("btn").addEventListener("click", startAudio);
there are still some issues witch i want to resolve and also there are some issues with the library witch need to be resolved

Import js file with TypeScript 2.0

Abstract
I'm trying to import ".js" file from an external location (i.e. node_modules) I'm trying to do this using commonjs module pattern, however import wouldn't want to work with ".js" file types until I add ".d.ts" file near ".js" file in the same folder.
But the problem is that I wouldn't want to affect any node_modules with my ".d.ts" files. I want it to be located in another folder, separate from node_modules but as soon as I do that, typescript compiler throws an error:
Example
I have the following folder structure:
|- DTS
| |- y.d.ts
|- main.ts
|- y.js
y.js has the following content
module.export = function (x) {
console.log(x);
};
y.d.ts has the following content
export interface Y {
(x): any;
}
declare let y: Y;
export default y;
main.ts has the following content
import * as y from './y'
Now when I'm trying to compile main.ts with:
tsc -m commonjs -t ES2015 main.ts
I will get an error:
x.ts(1,20): error TS2307: Cannot find module './y'.
Question
How to import ".js" files and being able to define it's ".d.ts" declarations while having both files located in different locations.
Edit
Here is the link to example project. Be sure to use TypeScript version 2.0 compiler. And the tsc command above to see the error.
Note: The official recommendation for proving your type definitions takes a slightly different approach than what is presented below.
I believe the approach below is slightly better as the *.d.ts file is practically identical to the final product.
During typechecking (build time) TypeScript works with *.ts files and (mostly) ignores *.js files.
Let me offer an example that motivates what (I believe) you are proposing.
Suppose there exists a perfectly good JavaScript library that sadly has no typings (e.g. N3).
Which has been installed via npm, thus:
npm install n3 --save
This, as is typical, is added to ./node_modules/n3/... and project.json.
As mentioned the typings does not exist and needs to be added manually.
I create an ./#types/n3.d.ts file for this purpose.
For our purposes it is not particularly important what the definitions actually are but something like the following is a good start:
declare namespace N3 {
}
declare module "n3" {
export = N3;
}
Now to your question.
Update the 'tsconfig.json':
...
"compilerOptions": {
"typeRoots": [
"node_modules/#types",
"#types"
],
...
"paths": {
"*": [
...
"./#types/*"
]
It will still be necessary to deal with the run-time resolution for locating the corresponding *.js files but that is a different question than the one you asked.
For reference you may find What is new in TypeScript
and this discussion thread useful.
This approach works fine when working with global variables but not so much with modules.
Update the 'tsconfig.json':
...
"paths": {
"*": [
...
"./#types/*"
],
"foo": [ "./#types/foo.d.ts" ]
},
...
being able to define it's ".d.ts" declarations while having both files located in different locations.
import follows the same module resolution process when given a .js or .d.ts file with relative files.

Categories