BabelJS class ordering when using inheritance [duplicate] - javascript

This question already has an answer here:
Bundling ES6 classes with Webpack. Is there a way to hoist extended classes?
(1 answer)
Closed 5 years ago.
I am trying to find a way to make babel load files in a particular order so that superclasses are loaded before childclasses.
An example given the following files:
src/fruit.js:
export class Fruit{
constructor(color){
this._color = color;
}
}
src/apple.js:
export class Apple extends Fruit{
constructor(){
super("green");
}
}
src/xecute.js:
var theApple = new Apple();
package.json
{
"name": "fruit",
"version": "1.0.0",
"description": "Fruit JS",
"scripts": {
"build": "babel src -o out/fruit-bundle.js"
},
"author": "Toby Nilsen",
"license": "MIT",
"devDependencies": {
"babel-cli": "^6.22.2",
"babel-preset-es2015": "^6.5.0"
}
}
.babelrc:
{
"presets": ["es2015"]
}
When I compile my files the following command
npm run build
And run my out/fruit-bundle.js with:
node out\fruit-bundle.js
I get the follwing error:
TypeError: Super expression must either be null or a function, not undefined
This is because babel parses apple.js before fruit.js. I can work around the problem by renaming my files to 0_fruit.js and 1_apple.js, but I would like to know if there is any way for babel to resolve the dependencies and order the output so that superclasses are loaded first?

Babel is just a transpiler. It just transpiles the syntax, but it does not do bundling for you. You'll need a bundler to resolve dependencies in the correct order. Consider checking out Rollup or Webpack. Going with Rollup, the simplest way to do this without the caching and other build optimizations is either to:
Run Rollup to bundle everything to one file then run Babel on Rollup's output.
Run Babel on all files, then use Rollup to bundle them all.
Also, so that the bundler knows the right order, import Fruit from Apple.
import Fruit from 'fruit';
export class Apple extends Fruit{
constructor(){
super("green");
}
}

Related

How to import js module when Typescript declaration file is located in a separate directory?

Question:
When I run npm run build with the configuration below, rollup.js is unable to resolve the dependency (import) and displays the following message below. Is there any way to make rollup happy while also referencing the Typescript declaration file?
Message from rollup:
(!) Unresolved dependencies
https://rollupjs.org/guide/en/#warning-treating-module-as-external-dependency
pdfjs-dist/types/web/ui_utils (imported by index.ts)
Here is my index.ts:
import { RendererType } from 'pdfjs-dist/types/web/ui_utils'
const renderType = RendererType.CANVAS;
My package.json:
{
"name": "myproject",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "rollup --config"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"#rollup/plugin-node-resolve": "^13.2.1",
"#rollup/plugin-typescript": "^8.3.2",
"pdfjs-dist": "^2.13.216",
"rollup": "^2.70.2",
"typescript": "^4.6.4"
}
}
My rollup.config.js:
import typescript from '#rollup/plugin-typescript';
import { nodeResolve } from '#rollup/plugin-node-resolve';
export default [
{
input: 'index.ts',
output: {
format: 'es',
file: 'index.js'
},
plugins: [
typescript(),
nodeResolve({ browser: true })
]
}
]
Here are the exact steps to reproduce the error above:
Create an empty folder and then run npm -y init
Run the following command:
npm install typescript pdfjs-dist rollup #rollup/plugin-node-resolve #rollup/plugin-typescript --save-dev
Add "build": "rollup --config" to your package.json
Create the rollup.config.js file shown above
Run npm run build in the terminal
More background:
Now, I should point out that the file pdfjs-dist/types/web/ui_utils is a typescript declaration file (ui_utils.d.ts). The actual js file is in pdfjs-dist/lib/web.
If I copy the typescript declaration file so that it is located in the same directory as the js file, dependency resolution works. However, since I will be writing a wrapper around pdf js, I would have to do this for every typescript declaration file which is very tedious and upgrading would also become an issue.
So another way to word the question would be how to resolve a module *.d.ts when the js file is located in another directory?
I came up with the following solution to the problem.
Create a d.ts with the following and name it the same as the module name (ui_utils.d.ts in my case)
declare module 'pdfjs-dist/lib/web/ui_utils' {
export * from 'pdfjs-dist/types/web/ui_utils'
}
Using the above, now I can reference the actual location of the module and Typescript will pick up the declarations as well.
import { RendererType } from 'pdfjs-dist/lib/web/ui_utils'
Side note: When using rollup, you may also need to use #rollup/plugin-commonjs to be able to resolve dependencies.

CLI for todo App - help debugging current solution using Commander or change approach to Readline and Event Emitter?

I am trying to build a CLI for a node js only todo app using commander and conf modules in node js, with chalk to colour the output . I am not sure how to resolve the errors being returned:
ReferenceError: require is not defined in ES module scope, you can use import instead
This file is being treated as an ES module because it has a '.js' file extension
contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.
I'm getting the above error for both conf and commander
Any suggestions on how I could go about debugging this, or changing approach to using readline and events/EventEmitter would be better, will be appreciated, Thanks
Below is a REDACTED version of code:
list.js
const conf = new (require('conf'))();
const chalk = require('chalk');
function list() {
const todoList = conf.get('todo-list');
if (todoList && todoList.length) {
console.log(
chalk.blue.bold(
'Tasks in green are done. Tasks in yellow are still not done.'
)
}
}
module.exports = list;
index.js file
const { program } = require('commander');
const list = require('./list');
program.command('list').description('List all the TODO tasks').action(list);
program.command('add <task>').description('Add a new TODO task').action(add);
program.parse();
package.json file
{
"main": "index.js",
"type": "module",
"keywords": [],
"dependencies": {
"chalk": "^5.0.0",
"chalk-cli": "^5.0.0",
"commander": "^8.3.0",
"conf": "^10.1.1"
},
"bin": {
"todos": "index.js"
}
}
In your package.json you have:
"type": "module",
This means files with the .js suffix are assumed to be ECMAScript rather than CommonJS. If you want to use CommonJS you can change the file suffix or change the "type" property.
Or you can use the new syntax. In ECMAScript you use import, in CommonJS you use require.
To read more about "type" see: https://nodejs.org/dist/latest-v16.x/docs/api/packages.html#determining-module-system
After some more research I found out I was 'muddying the waters' between CJS or ESM modules.
CJS modules use require and that is the old way of doing things prior to ES6 modules
ESM modules use import
My package.json says type: module telling NodeJS that I am using ESM. But the code is saying CJS.
These are the steps I take to fix this:
rename index.js to index.mjs
update package.json accordingly
replace all require calls with import statements
replace module.exports = list with default export = list (or used a named export)

Parcel Bundler beautify, lint, and create .min.js

I'm new the world of automating/testing/bunding with JS and I've got parcel setup for the most part but I noticed that when it builds files, it does not actually save them with the .min.js part in the file name. I'm wondering if theres a way to do this without having to rename the build file manually.
I'm also trying to find a way to have parcel go through the original source files(the ones that you work on) and lint and beautify them for me
Here's what my package.json looks like
{
"name": "lpac",
"version": "1.3.1",
"description": "",
"dependencies": {},
"devDependencies": {
"parcel": "^2.0.0-rc.0"
},
"scripts": {
"watch": "parcel watch --no-hmr",
"build": "parcel build"
},
"targets": {
"lite-maps": {
"source": ["./path/file1.js", "./path/file2.js", "./path/file3.js"],
"distDir": "./path/build/"
}
},
"browserslist": "> 0.5%, last 2 versions, not dead",
"outputFormat" : "global",
}
I checked out the docs but I couldn't find anything on linting or beautifying with parcel. How can i go about doing that? If you have tutorial links to doing so please also share because resources/tutorials seem scarce for anything other than the basic watching and building files
Unfortunately, there is no out-of-the-box setting that can cause parcel javascript output look like [fileName].[hash].min.js instead of [fileName].[hash].js. The .min.js extension is just a convention to keep output files distinct from source files, though - it has no effect at runtime - and the fact that parcel does automatic content hashing makes it easy enough to tell this. And even though they don't have a .min.js extension, these output files are definitely still minified and optimized by default.
However, if you really, really want this anyways, it's relatively simple to write a Namer plugin for parcel that adds .min.js to all javascript output:
Here's the code:
import { Namer } from "#parcel/plugin";
import path from "path";
export default new Namer({
name({ bundle }) {
if (bundle.type === "js") {
const filePath = bundle.getMainEntry()?.filePath;
if (filePath) {
let baseNameWithoutExtension = path.basename(filePath, path.extname(filePath));
// See: https://parceljs.org/plugin-system/namer/#content-hashing
if (!bundle.needsStableName) {
baseNameWithoutExtension += "." + bundle.hashReference;
}
return `${baseNameWithoutExtension}.min.js`;
}
}
// Returning null means parcel will keep the name of non-js bundles the same.
return null;
},
});
Then, supposing the above code was published in a package called parcel-namer-js-min, you would add it to your parcel pipeline with this .parcelrc:
{
"extends": "#parcel/config-default",
"namers": ["parcel-namer-js-min", "..."]
}
Here is an example repo where this is working.
The answer to your second question (is there "a way to have parcel go through the original source files(the ones that you work on) and lint and beautify them for me") is unfortunately, no.
However, parcel can work well side-by-side with other command line tools that do this do this. For example, I have most of my projects set up with a format command in the package.json, that looks like this:
{
...
"scripts": {
...
"format": "prettier --write src/**/* -u --no-error-on-unmatched-pattern"
}
...
{
You can easily make that command automatically run for git commits and pushes with husky.

Babel 6 with just JSX transform and no other transforms

I want to use babel-plugin-transform-jsx, but no other transforms on some JSX files with some Javascript currently considered at stage 3 i.e. candidates.
Transpilation fails with a Syntax Error if those JSX files contain:
rest spread operators {...x, ...y}
async generators async function * () {}
The objective is to improve debug-ability of code in a modern browser, since the Babel transpilation of async generators in particular seems to break the dev tools for Chrome, Firefox i.e. breakpoints stop working, references to this fail, debugger calls are skipped, and numerous other observed problems.
There seems to be no alternative to using Babel to generate JSX in the above form — which works fine; an ideal solution would be to just have Babel ignore the async generators and rest-spread operators (and any other code it'd otherwise throw a syntax error on).
EDIT
Using the plugin suggested by Victor appears to be the correct solution but running babel-plugin-syntax-async-generators on this:
class SearchCriteria {
async * results (authManager) { }
}
Causes the error:
Unexpected token, expected "(" (2:10)
1 | class SearchCriteria {
> 2 | *async results(authManager) {
| ^
Reproducible here, when you add the syntax-async-generators plugin.
Babel has transform plugins and syntax plugins. Transform plugins apply transformations to your code. Syntax plugins allow Babel to parse specific types of JavaScript syntax, not transform it. Transform plugins will enable the corresponding syntax plugin so you don't have to specify both.
So in your case what you need is babel-plugin-transform-jsx transform plugin and two syntax plugins: babel-plugin-syntax-async-generators, babel-plugin-syntax-object-rest-spread.
The corresponding .babelrc will be:
{
plugins: ["babel-plugin-transform-jsx", "babel-plugin-syntax-async-generators", "babel-plugin-syntax-object-rest-spread"]
}
Minimal package.json:
{
"name": "babel-jsx",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"build": "babel index.js"
},
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-plugin-syntax-async-generators": "^6.13.0",
"babel-plugin-syntax-object-rest-spread": "^6.13.0",
"babel-plugin-transform-jsx": "^2.0.0",
"react": "^16.4.1"
}
}
And if you have JavaScript code like this in index.js:
import React from 'react'
const MyComp = <div>Hello</div>;
async function* myfunc() {
const array = [1, 2, 3];
console.log(...array);
}
And run commands:
yarn
yarn build
Then the output will be:
$ babel index.js
import React from 'react';
const MyComp = {
elementName: 'div',
attributes: {},
children: ['Hello']
};
async function* myfunc() {
const array = [1, 2, 3];
console.log(...array);
}

Conditional import of peerDependency in es6

I am working on a JavaScript i18n library which localize dates (among other types and objects).
It is currently relying on moment.js, which is defined as a peerDependency (localization is one of the features but not the only one, it might not be used)
// package.json
{
"name": "my-i18n-library",
// ...
"scripts": {
// ...
"clean": "rimraf build",
"build": "babel src -d build",
"prepare": "npm run clean && npm run build"
},
"peerDependency": {
"moment": "~2.20.1",
"date-fns": "~1.29.0"
},
// ...
}
// .babelrc
{
"presets": ["env", "stage-1", "react"]
}
Basically something like (a bit more error-proof but I simplified the logic) :
import Moment from 'moment.js'
import 'moment/min/locales'
class Localizer {
localizeDate(value, locale, displayFormat = "L", parseFormat = "LTS") {
return Moment(date, parseFormat, locale).format(displayFormat);
}
}
Problem is, if moment.js is a nice work, it is like a backpack of stones, you would not bring it on a 50 miles trail, especially if you only need to localize one date in a whole application. Bandwidth-wise, it's not worth it IMO (actually in a lot of people opinions as well).
So I am considering switching to lighter libraries such as date-fns, but I figured out an option I think is even better :
what if we could let the other choose which library suits him the most?
I was thinking to define different implementations of library-related localizers, and conditionally import them depending of which peerDependency is installed :
// /Date/DateFnsLocalizer.js
import { parse } from 'date-fns/parse'
import { format } from 'date-fns/format'
class DateFnsLocalizer {
localizeDate(value, locale, displayFormat = "L") {
return format(parse(date), displayFormat, { locale })
}
}
Is that even possible in JavaScript ?
// /Localizer.js
if (isModuleDefined('moment.js')) {
import BaseLocalizer from './Date/MomentLocalizer'
} else if (isModuleDefined('date-fns')) {
import BaseLocalizer './Date/DateFnsLocalizer'
} else if (isModuleDefined('some-other-lib')) {
import BaseLocalizer './Date/SomeOtherLibLocalizer'
} else {
throw new Error('No date library defined! Please install at least one of ["moment.js", "date-fns", "some-other-lib"]')
}
export default class Localizer extends BaseLocalizer
I think "import" statements have to be made as first statements in the file. Maybe using require instead... (as suggested in ES6: Conditional & Dynamic Import Statements) ? And is it possible to test a module existence without importing it (basically how to code that isModuleDefined() method?)
I have seen those ones as well :
ES6 variable import name in node.js?
How can I conditionally import an ES6 module?
But as we are currently using babel to transpile this library, if this architecture is possible, could it cause compilation troubles in other building tools such as webpack, gulp, grunt etc.?

Categories

Resources