Bundle NPM Package so it has different import paths with Vite and Typescript - node.js

How can I bundle my NPM package in a way that I can have different import paths for different parts of the package? I have found webpack approaches, but I am using Vite and TS.
My package looks like this:
- src
- atoms
- molecules
- organism
- index.ts (currently simply imports and exports everything)
Now I can use this currently like this
import { Button } from '#mypackage/library'
How can I do it, so I get this outcome:
import { Button } from '#mypackage/library/atom'
Here is the relevant part of my package.json
{
"entry": "src/index.ts",
"main": "dist/index.cjs.js",
"module": "dist/index.es.js",
"types": "dist/index.d.ts",
"files": [
"dist",
"src"
],
"exports": {
".": {
"import": "./dist/index.es.js",
"require": "./dist/index.cjs.js",
"types": "./dist/index.d.ts"
},
"./package.json": "./package.json",
"./atoms": "./src/atoms/index.ts",
"./molecules": "./src/molecules/index.ts",
"./organisms": "./src/organisms/index.ts",
"./theme": "./src/theme/index.ts"
},
}
Here is my vite.config.ts
export default defineConfig({
build: {
lib: {
entry: path.resolve(__dirname, 'src/index.ts'),
formats: ['es', 'cjs'],
name: '#workdigtital/component-library-react',
fileName: (format) => `index.${format}.js`
},
rollupOptions: {
external: ['react', 'react-dom'],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM'
},
exports: 'named'
}
}
},
plugins: [react(), dts({ insertTypesEntry: true })],
resolve: {
alias: {
'#': path.resolve(__dirname, './src')
}
}
});
If I currently try an import like this, inside another project (Laravel+React), in which installed the library.
import { ThemeProvider } from '#workdigital/component-library-react/theme';
I get the following run time error (But no Typescript errors, even IntelliSense is working):
Failed to load url /resources/js/theme/ThemeProvider (resolved id: /resources/js/theme/ThemeProvider). Does the file exist?
My resulting Dist folder looks like this:

You can't have TypeScript exports, this simply won't work. An npm package should have only JS exports.
If you want to be able to selectively import different parts of your package, you must transpile them to different files.
rollup can do it, but it is lots of work, as you will have to set up a separate target for each exported file. Normally you use rollup to create a single bundle, this what this tool is made for.
tsc with a tsconfig.json will be a much better choice in your case. It does this by default, you only need to specify the output directory and it will produce a separate file for each source.
There is an excellent guide on the TypeScript site about packaging TypeScript libraries, you should probably start there.

Related

React: Absolute Paths from Root Folder using ViteJS with TypeScript

So, in React, using vite, I'm trying to do the following structure, but seems I can't get it to work because I'm missing a concept or something, so the structure is as follows:
src/utils
src/routes
src/index.tsx
src/main.tsx
And on the index.tsx, I want to import utils and routes, and then call them at any root level as following: import {Routes, Utils} from "#", but the way I did is not working.
Meanwhile, this is how I configured it with vite:
resolve: {
alias: {
"#": path.resolve(__dirname, "src"),
},
},
Make sure index.tsx exports everything from src/utils and src/routes:
// src/index.tsx
export * from './routes'
export * from './utils'
And configure TypeScript with a path alias for #:
// tsconfig.json
{
"compilerOptions": {
"paths": {
"#": ["./src"], // 👈 needed for barrel imports from '#'
"#/*": ["./src/*"]
}
}
}
demo

Node.js: How to import test files in custom test runner

I'm trying to create my own custom testing framework for learning purpose. Test files are written in following way
import { somemethod } from './some/module'
test(/I click on a button)/, () => {
browser.get("someSelector").should("have.text",somemethod());
});
I user require(file) to load test files. But it throw error SyntaxError: Unexpected token {
for import statement in test file. I'm using node js version 11.15.
If I switch to node v13.14 and define "type": "module" in my package.json then it doesn't let me use require(file) to load a test file or any module in my package.
How can I import tests files considering the user may be importing the modules using import or require?
This answer is very empirical...
Considering that it works using canonical commonjs approach you can try to debug it with newer version of NODE (currently I would use 14). For it, I would suggest you to use a node version manager like NVM so you can switch between node version easily and test that accordling seeing differences between various node installations.
Make a minimal project with npm init with a single dependency, save your index with the .mjs extension and try an import the above dependency. If you are be able to import that dependency with that minimal environment you can blame either your previous node or your configuration or both of them.
At the moment you should only create a small 2 files project to reproduce the problem. It seems your current node does not consider the "type": "module" configuration and runs everything in its classic way.
Regarding your comments....
As far as I know import can be used even in your code, not just at the beginning:
(async () => {
if (somethingIsTrue) {
// import module for side effects
await import('/modules/my-module.js');
}
})();
from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
Additionally you can try Webpack with a configuration like:
// webpack.config.js
const nodeExternals = require('webpack-node-externals');
module.exports = {
mode: 'production',
target: 'node',
externals: [nodeExternals()],
entry: {
'build/output': './src/index.js'
},
output: {
path: __dirname,
filename: '[name].bundle.js',
libraryTarget: 'commonjs2'
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
['env', {
'targets': {
'node': 'current'
}
}]
]
}
}
}]
}
};
With NodeExternals you don't put your node dependencies in the bundle but only your own code. You refer to node_modules for the rest. You might not want that.

Jest Is Not Transpiling ES6 Modules In Test (SyntaxError: Unexpected token export)

I'm at a boiling point with getting Jest to understand ES6 modules import/export syntax and it is hindering my project development progress. My project structure is the following:
root module
org-domain (ES6)
org-services (ES6)
react-ui-module (React via CRA2)
org-services has a local path dependency on org-domain in its package json:
// in org-services package.json
"dependencies": {
"#org/domain": "file:../org-domain",
},
My .babelrc in org-services is the following:
{
"env": {
"test": {
"presets": ["#babel/preset-flow", "#babel/preset-env"]
"plugins": ["#babel/plugin-transform-modules-commonjs"]
}
},
"presets": [
"#babel/preset-flow",
["#babel/preset-env", {
"targets": {
"esmodules": true
}
}]
],
"plugins": [
["module-resolver", {
"root": ["./node_modules/#org/domain"],
"alias": {
"#org/constants": "./node_modules/#org/domain/src/constants",
"#org/contracts": "./node_modules/#org/domain/src/request-contracts"
}
}]
]
}
I do not know if the problem is due to how I am including my dependencies so I'm going to add the finer details of anything related to my import/export of these modules for the sake of clarity.
In the implementation files of org-services I am importing org-domain using npm scoped syntax like so: import ... from '#org/domain
Here are some observations I have:
In local development, when I try to reference click #org/domain, instead of being directed to org-services/node_modules/#org/domain I get redirected to the actual relative directory location which is root/org-services. I believe Jest ignores node_modules (correct me if I am wrong) but in my jest.config.js for org-services, I have:
collectCoverage: true,
coverageDirectory: 'coverage',
coveragePathIgnorePatterns: [
'/node_modules/'
],
moduleDirectories: [
'src',
'node_modules'
],
moduleFileExtensions: [
'js'
],
transform: {
'^.+\\.js$': 'babel-jest'
},
transformIgnorePatterns: [
'node_modules/(?!#org/domain)$'
]
To my understanding, everything should just work right now with all the configuration I have with respect to setting the plugin #babel/plugin-transform-modules-commonjs in test (within .babelrc) and including the '^.+\\.js$': 'babel-jest' instruction under the transform key in jest.config.js located under org-services -- but it does not.
I have tried every single thing I could find online with respect to this issue with no success. I have not gotten anywhere since and my patience is lost with this testing framework and the lack of support for ES6. It should not be this hard, so clearly I am doing something wrong here. Please advise.
Update 1
Found another SO post that is a mirror of this situation I am in.
Jest fails to transpile import from npm linked module
Unfortunately, the provided solution does not work for my case.
After upgrading from #babel/xyz: 7.0.0 to 7.1.2 I started getting an error regarding "import"
Jest encountered an unexpected token
<snip>
Details:
C:\....\SstcStrategy.test.js:2
import sequelizeFixtures from 'sequelize-fixtures';
^^^^^^
SyntaxError: Unexpected token import
at ScriptTransformer._transformAndBuildScript (node_modules/jest-runtime/build/script_transformer.js:403:17)
To fix this I had to add #babel/plugin-transform-modules-commonjs as you mention in your question.
My babel.config.js now looks like this:
module.exports = {
presets: [
[
'#babel/preset-env',
{
targets: {
node: '8.10',
},
debug: false,
},
],
],
ignore: ['node_modules'],
plugins: [
'#babel/plugin-transform-runtime',
'#babel/plugin-transform-modules-commonjs',
// Stage 2
['#babel/plugin-proposal-decorators', { legacy: true }],
'#babel/plugin-proposal-function-sent',
'#babel/plugin-proposal-export-namespace-from',
'#babel/plugin-proposal-numeric-separator',
'#babel/plugin-proposal-throw-expressions',
// Stage 3
'#babel/plugin-syntax-dynamic-import',
'#babel/plugin-syntax-import-meta',
['#babel/plugin-proposal-class-properties', { loose: false }],
'#babel/plugin-proposal-json-strings',
],
};
Also BTW you don't need to define babel-jest transform in jest.config.js as this is the default setting.
Hope this helps

Jest gives an error: "SyntaxError: Unexpected token export"

I'm using Jest to test my React app.
Recently, I added DeckGL to my app. My tests fail with this error:
Test suite failed to run
/my_project/node_modules/deck.gl/src/react/index.js:21
export {default as DeckGL} from './deckgl';
^^^^^^
SyntaxError: Unexpected token export
at ScriptTransformer._transformAndBuildScript (node_modules/jest-runtime/build/script_transformer.js:318:17)
at Object.<anonymous> (node_modules/deck.gl/dist/react/deckgl.js:9:14)
at Object.<anonymous> (node_modules/deck.gl/dist/react/index.js:7:15)
This looks like an issue with Jest transforming a node module before running it's tests.
Here is my .babelrc:
{
"presets": ["react", "es2015", "stage-1"]
}
Here is my jest setup:
"jest": {
"testURL": "http://localhost",
"setupFiles": [
"./test/jestsetup.js"
],
"snapshotSerializers": [
"<rootDir>/node_modules/enzyme-to-json/serializer"
],
"moduleDirectories": [
"node_modules",
"/src"
],
"moduleNameMapper": {
"\\.(css|scss)$": "<rootDir>/test/EmptyModule.js"
}
},
I seem to have the correct things necessary to transform export {default as DeckGL }. So any ideas whats going wrong?
This means, that a file is not transformed through TypeScript compiler, e.g. because it is a JS file with TS syntax, or it is published to npm as uncompiled source files. Here's what you can do.
Adjust your transformIgnorePatterns allowed list:
{
"jest": {
"transformIgnorePatterns": [
"node_modules/(?!#ngrx|(?!deck.gl)|ng-dynamic)"
]
}
}
By default Jest doesn't transform node_modules, because they should be valid JavaScript files. However, it happens that library authors assume that you'll compile their sources. So you have to tell this to Jest explicitly. Above snippet means that #ngrx, deck and ng-dynamic will be transformed, even though they're node_modules.
And if you are using 'create-react-app', it won't allow you to specify 'transformIgnorePatterns' via Jest property in package.json
As per this https://github.com/facebook/create-react-app/issues/2537#issuecomment-390341713
You can use CLI as below in your package.json to override and it works :
"scripts": {
"test": "react-scripts test --transformIgnorePatterns \"node_modules/(?!your-module-name)/\"",
},
This is because Node.js cannot handle ES6 modules.
You should transform your modules to CommonJS therefore.
Babel 7 >=
Install
npm install --save-dev #babel/plugin-transform-modules-commonjs
And to use only for test cases add to .babelrc,
Jest automatically gives NODE_ENV=test global variable.
"env": {
"test": {
"plugins": ["#babel/plugin-transform-modules-commonjs"]
}
}
Babel 6 >=
npm install --save-dev babel-plugin-transform-es2015-modules-commonjs
to .babelrc
"env": {
"test": {
"plugins": ["transform-es2015-modules-commonjs"]
}
}
Jest by default won't compile files in the node_modules directory.
transformIgnorePatterns [array]
Default: ["/node_modules/"]
An array of regexp pattern strings that are matched against all source
file paths before transformation. If the test path matches any of the
patterns, it will not be transformed.Default: ["/node_modules/"]
DeckGL seems to be in ES6, to make jest able to read it, you need to compile this as well.
To do that, just add an exception for DeckGL in the transformignorePatterns
"transformIgnorePatterns": ["/node_modules/(?!deck\.gl)"]
https://facebook.github.io/jest/docs/en/configuration.html#transformignorepatterns-array-string
I was having this issue with a monorepo. A package in the root node_modules was breaking my tests. I fixed by changing my local .babelrc file to babel.config.js. Explanation: https://github.com/facebook/jest/issues/6053#issuecomment-383632515
It was work around #1 on this page that fixed it for me though workaround #2 on that page is mentioned in above answers so they may also be valid.
"Specify the entry for the commonjs version of the corresponding package in the moduleNameMapper configuration"
jest.config.js
moduleNameMapper: {
"^uuid$": require.resolve("uuid"),
"^jsonpath-plus$": require.resolve("jsonpath-plus")
...
In my case I use this config in the file package.json:
"jest": {
"transformIgnorePatterns": [
"!node_modules/"
]
}
This code worked for me
// .babelrc
{
"presets": [
["env", {
"modules": "commonjs", // <- Check and see if you have this line
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}],
"stage-2"
],
"plugins": ["transform-vue-jsx", "transform-runtime"],
"env": {
"test": {
"presets": ["env", "stage-2"],
"plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"]
}
}
}
jest understands commonJs so it needs babel to transform the code for it before use. Also jest uses caching when running code. So make sure you run jest --clearCache before running jest.
Tested Environment:
Node v8.13.0
Babel v6+
Jest v27
I'm using a monorepo (it contains multiple packages for the frontend and backend).
The package I'm testing imports a file from another package that uses the dependency uuid.
All the files are in Typescript (not Javascript).
The package I'm testing has a tsconfig file for testing only, called tsconfig.test.json. It has the properties commonjs and allowJs. Adding allowJs solves the problem when importing uuid, I don't know why.
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": [
"jest",
"node"
],
// Necessary to import dependency uuid using CommonJS
"allowJs": true
},
"include": [
"jest.config.ts",
"**/*.test.ts",
"**/*.d.ts"
]
}
I was upgrading a project that uses a version of babel that reads the config from .babelrc, when I upgraded to a newer version I read:
https://babeljs.io/docs/en/configuration#whats-your-use-case
What's your use case?
You are using a monorepo?
You want to compile node_modules?
babel.config.json is for you!
On top of:
{
"jest": {
"transformIgnorePatterns": [
"node_modules/(?!(module))"
]
}
}
I renamed .babelrc to babel.config.json too.
I had the same error of importing dataSet from vis-data.js library
import { DataSet } from 'vis-data/esnext';
So I just removed /esnext from the path and now it works:
import { DataSet } from 'vis-data';

Loading a static file in Angular during build

I'm want to load the contents of a file and inject it as a string in TypeScript at build time. I understand that this code would ordinarily be server code, but what I want is to have a build step that reads the file and injects its contents as a string.
import { readFileSync } from 'fs';
import { Component } from '#angular/core';
#Component({
template: `<pre>${readFileSync('./example.code')}</pre>`
})
export class ExampleComponent { }
Assuming example.code just has "Hello World" I would want this file to be built as:
template: `<pre>"Hello World"</pre>`
I have found babel-plugin-static-fs which I think should allow me to do this, but I was originally using ng (angular-cli) to build the project. I have done ng eject and updated webpack:
module: {
rules: [
/* snip */
{
"test": /\.ts$/,
"use": [
{
loader: "babel-loader",
options: {
plugins: ['babel-plugin-static-fs']
}
},
{
"loader": "#ngtools/webpack"
}, ] } ] }
However, when I run webpack, I still get
Cannot find module 'fs'
If I reverse the order of the loaders, it seems like babael does not like the # used in may annotations such as the #Component above so that loader does not work.
Is there any way to load a file as static content during an Angular project build?
The issue here is actually related to the tsconfig.app.json file that Angular creates and uses for AoT. This is separate from the tsconfig.json used to actually build the project which does load #types/node as expected.
If you've created a project with ng new, you can change tsconfig.app.json:
- "types": [],
+ "types": ["node"],
This will have the AoT compiler use the type definitions from #types/node.

Resources