import-jsx
Advanced tools
Comparing version 4.0.1 to 5.0.0
89
cache.js
@@ -1,44 +0,33 @@ | ||
'use strict'; | ||
// Based on https://github.com/babel/babel-loader/blob/15df92fafd58ec53ba88efa22de7b2cee5e65fcc/src/cache.js | ||
const fs = require('fs'); | ||
const os = require('os'); | ||
const path = require('path'); | ||
const crypto = require('crypto'); | ||
const makeDir = require('make-dir'); | ||
const findCacheDir = require('find-cache-dir'); | ||
const transform = require('./transform'); | ||
import fs from 'node:fs/promises'; | ||
import os from 'node:os'; | ||
import path from 'node:path'; | ||
import crypto from 'node:crypto'; | ||
import makeDir from 'make-dir'; | ||
import findCacheDir from 'find-cache-dir'; | ||
import packageConfig from './package.json' assert {type: 'json'}; | ||
let directory; | ||
const cacheDirectory = findCacheDir({name: 'import-jsx'}) || os.tmpdir(); | ||
/** | ||
* Build the filename for the cached file | ||
* | ||
* @param {String} source Original contents of the file to be cached | ||
* @param {Object} options Options passed to importJsx | ||
* @param {String} version Version of import-jsx | ||
* | ||
* @return {String} | ||
*/ | ||
const filename = (source, options, version) => { | ||
const hash = crypto.createHash('md5'); | ||
const contents = JSON.stringify({source, options, version}); | ||
hash.update(contents); | ||
export const cacheKeyFromSource = source => { | ||
const contents = JSON.stringify({ | ||
source, | ||
version: packageConfig.version | ||
}); | ||
return hash.digest('hex') + '.js'; | ||
return crypto.createHash('md5').update(contents).digest('hex') + '.js'; | ||
}; | ||
/** | ||
* Handle the cache | ||
* | ||
* @params {String} directory | ||
* @params {Object} parameters | ||
*/ | ||
const handleCache = (directory, parameters) => { | ||
const {modulePath, options, source, version} = parameters; | ||
const cachedTransform = async ( | ||
transform, | ||
parameters, | ||
directory = cacheDirectory | ||
) => { | ||
const {enabled, key} = parameters; | ||
if (!options.cache) { | ||
return transform(source, options, modulePath); | ||
if (!enabled) { | ||
return transform(); | ||
} | ||
const file = path.join(directory, filename(source, options, version)); | ||
const file = path.join(directory, key); | ||
@@ -48,5 +37,4 @@ try { | ||
// we just need to return it | ||
return fs.readFileSync(file).toString(); | ||
// eslint-disable-next-line no-unused-vars | ||
} catch (error) {} | ||
return await fs.readFile(file, 'utf8'); | ||
} catch {} | ||
@@ -57,6 +45,6 @@ const fallback = directory !== os.tmpdir(); | ||
try { | ||
makeDir.sync(directory); | ||
await makeDir(directory); | ||
} catch (error) { | ||
if (fallback) { | ||
return handleCache(os.tmpdir(), parameters); | ||
return cachedTransform(transform, parameters, os.tmpdir()); | ||
} | ||
@@ -69,10 +57,10 @@ | ||
// return it to the user asap and write it in cache | ||
const result = transform(source, options, modulePath); | ||
const result = await transform(); | ||
try { | ||
fs.writeFileSync(file, result); | ||
await fs.writeFile(file, result); | ||
} catch (error) { | ||
if (fallback) { | ||
// Fallback to tmpdir if node_modules folder not writable | ||
return handleCache(os.tmpdir(), parameters); | ||
return cachedTransform(transform, parameters, os.tmpdir()); | ||
} | ||
@@ -86,17 +74,2 @@ | ||
/** | ||
* Retrieve file from cache, or create a new one for future reads | ||
* | ||
* @param {Object} parameters | ||
* @param {String} parameters.modulePath | ||
* @param {String} parameters.source Original contents of the file to be cached | ||
* @param {Object} parameters.options Options passed to importJsx | ||
* @param {String} parameters.version Version of import-jsx | ||
*/ | ||
module.exports = parameters => { | ||
if (!directory) { | ||
directory = findCacheDir({name: 'import-jsx'}) || os.tmpdir(); | ||
} | ||
return handleCache(directory, parameters); | ||
}; | ||
export default cachedTransform; |
84
index.js
@@ -1,67 +0,43 @@ | ||
'use strict'; | ||
const path = require('path'); | ||
const resolveFrom = require('resolve-from'); | ||
const callerPath = require('caller-path'); | ||
const cache = require('./cache'); | ||
const {version} = require('./package.json'); | ||
import process from 'node:process'; | ||
import cachedTransform, {cacheKeyFromSource} from './cache.js'; | ||
import transform from './transform.js'; | ||
const importJsx = (moduleId, options) => { | ||
if (typeof moduleId !== 'string') { | ||
throw new TypeError('Expected a string'); | ||
export const load = async (url, _context, nextLoad) => { | ||
if (!url.endsWith('.js') || url.includes('node_modules')) { | ||
return nextLoad(url); | ||
} | ||
options = { | ||
pragma: 'h', | ||
pragmaFrag: 'Fragment', | ||
cache: true, | ||
...options | ||
}; | ||
const result = await nextLoad(url); | ||
const modulePath = resolveFrom(path.dirname(callerPath()), moduleId); | ||
if (!options.cache) { | ||
delete require.cache[modulePath]; | ||
if (!result.source) { | ||
return result; | ||
} | ||
// If they used .jsx, and there's already a .jsx, then hook there | ||
// Otherwise, hook node's default .js | ||
const ext = path.extname(modulePath); | ||
const hookExt = require.extensions[ext] ? ext : '.js'; | ||
const source = result.source.toString(); | ||
const oldExtension = require.extensions[hookExt]; | ||
const useCache = | ||
process.env.IMPORT_JSX_CACHE !== '0' && | ||
process.env.IMPORT_JSX_CACHE !== 'false'; | ||
require.extensions[hookExt] = module => { | ||
const oldCompile = module._compile; | ||
const cacheKey = cacheKeyFromSource(source); | ||
module._compile = source => { | ||
const result = cache({ | ||
modulePath, | ||
options, | ||
source, | ||
version | ||
}); | ||
try { | ||
const transformedSource = await cachedTransform( | ||
() => { | ||
return transform(source, url); | ||
}, | ||
{ | ||
enabled: useCache, | ||
key: cacheKey | ||
} | ||
); | ||
module._compile = oldCompile; | ||
module._compile(result, modulePath); | ||
return { | ||
source: transformedSource, | ||
format: 'module', | ||
shortCircuit: true | ||
}; | ||
require.extensions[hookExt] = oldExtension; | ||
oldExtension(module, modulePath); | ||
}; | ||
const m = require(modulePath); | ||
require.extensions[hookExt] = oldExtension; | ||
if (!options.cache) { | ||
delete require.cache[modulePath]; | ||
} catch { | ||
return nextLoad(url); | ||
} | ||
return m; | ||
}; | ||
module.exports = importJsx; | ||
module.exports.default = importJsx; | ||
module.exports.create = options => { | ||
return moduleId => importJsx(moduleId, options); | ||
}; |
{ | ||
"name": "import-jsx", | ||
"version": "4.0.1", | ||
"version": "5.0.0", | ||
"description": "Require and transpile JSX on the fly", | ||
@@ -12,7 +12,9 @@ "license": "MIT", | ||
}, | ||
"type": "module", | ||
"exports": "./index.js", | ||
"engines": { | ||
"node": ">=10" | ||
"node": ">=14.16" | ||
}, | ||
"scripts": { | ||
"test": "xo && ava" | ||
"test": "xo && ava --serial" | ||
}, | ||
@@ -32,16 +34,18 @@ "files": [ | ||
"dependencies": { | ||
"@babel/core": "^7.5.5", | ||
"@babel/plugin-proposal-object-rest-spread": "^7.5.5", | ||
"@babel/plugin-transform-destructuring": "^7.5.0", | ||
"@babel/plugin-transform-react-jsx": "^7.3.0", | ||
"caller-path": "^3.0.1", | ||
"find-cache-dir": "^3.2.0", | ||
"make-dir": "^3.0.2", | ||
"resolve-from": "^3.0.0", | ||
"rimraf": "^3.0.0" | ||
"@babel/core": "^7.21.0", | ||
"@babel/preset-react": "^7.18.6", | ||
"find-cache-dir": "^4.0.0", | ||
"make-dir": "^3.1.0" | ||
}, | ||
"devDependencies": { | ||
"ava": "^0.19.1", | ||
"@babel/eslint-parser": "^7.19.1", | ||
"ava": "^5.2.0", | ||
"del": "^7.0.0", | ||
"execa": "^7.0.0", | ||
"preact": "^10.13.0", | ||
"preact-render-to-string": "^5.2.6", | ||
"prettier": "^2.0.2", | ||
"xo": "^0.28.1" | ||
"react": "^18.2.0", | ||
"react-dom": "^18.2.0", | ||
"xo": "^0.53.1" | ||
}, | ||
@@ -56,9 +60,18 @@ "prettier": { | ||
"xo": { | ||
"prettier": true, | ||
"parser": "@babel/eslint-parser", | ||
"parserOptions": { | ||
"requireConfigFile": false, | ||
"babelOptions": { | ||
"parserOpts": { | ||
"plugins": [ | ||
"importAssertions" | ||
] | ||
} | ||
} | ||
}, | ||
"ignore": [ | ||
"test/fixtures" | ||
], | ||
"rules": { | ||
"node/no-deprecated-api": "off" | ||
} | ||
] | ||
} | ||
} |
112
readme.md
# import-jsx ![Build Status](https://github.com/vadimdemedes/import-jsx/workflows/test/badge.svg) | ||
> Require and transpile JSX on the fly | ||
> Import and transpile JSX via [loader hooks](https://nodejs.org/dist/latest-v18.x/docs/api/esm.html#loaders). It doesn't transpile anything besides JSX and caches transpiled sources by default. | ||
- Doesn't install any `require()` hooks | ||
- Auto-detects React pragma (`React.createElement`) and falls back to `h` pragma supported by Preact and others | ||
- Caches transpiled sources by default | ||
- Bundles in [object rest spread](https://babeljs.io/docs/plugins/transform-object-rest-spread/) transform | ||
## Install | ||
```console | ||
npm install import-jsx | ||
``` | ||
$ npm install --save import-jsx | ||
``` | ||
## Usage | ||
```js | ||
const importJsx = require('import-jsx'); | ||
> **Note**: | ||
> `import-jsx` only works with ES modules. | ||
const reactComponent = importJsx('./react'); | ||
const preactComponent = importJsx('./preact'); | ||
const customComponent = importJsx('./custom', {pragma: 'x'}); | ||
```sh | ||
node --loader=import-jsx react-example.js | ||
``` | ||
**React** | ||
**react-example.js** | ||
```jsx | ||
const React = require('react'); | ||
module.exports = <div />; | ||
const HelloWorld = () => <h1>Hello world</h1>; | ||
``` | ||
**Preact** | ||
## Examples | ||
```jsx | ||
const {h} = require('preact'); | ||
### React | ||
module.exports = <div />; | ||
``` | ||
React is auto-detected by default and `react` will be auto-imported, if it's not already. | ||
**Any JSX pragma** | ||
```jsx | ||
const x = (tagName, attrs, ...children) => {}; | ||
module.exports = <div />; | ||
const HelloWorld = () => <h1>Hello world</h1>; | ||
``` | ||
## API | ||
### Preact | ||
### importJsx(moduleId, [options]) | ||
If an alternative library is used and exports `createElement`, like Preact, configure `import-jsx` to import it instead of React: | ||
#### moduleId | ||
```jsx | ||
/** @jsxImportSource preact */ | ||
Type: `string` | ||
const HelloWorld = () => <h1>Hello world</h1>; | ||
``` | ||
Module id. | ||
### Any JSX pragma | ||
#### options | ||
For libraries not compatible with React's API, but which still support JSX, import it and configure `import-jsx` to use its pragma: | ||
##### pragma | ||
```jsx | ||
/** @jsxRuntime classic */ | ||
/** @jsx h */ | ||
import h from 'vhtml'; | ||
Type: `string`<br> | ||
Default: `h` | ||
const HelloWorld = () => <h1>Hello world</h1>; | ||
``` | ||
Override [JSX pragma](https://jasonformat.com/wtf-is-jsx/). | ||
### CLI | ||
##### pragmaFrag | ||
`import-jsx` can be used to transpile JSX inside CLI entrypoints defined in `bin` section of `package.json` and their imported files. | ||
Type: `string`<br> | ||
Default: `Fragment` | ||
For example, given this **package.json**: | ||
Override pragma for [JSX fragments](https://babeljs.io/docs/en/babel-plugin-transform-react-jsx#pragmafrag). | ||
```json | ||
{ | ||
"name": "my-amazing-cli", | ||
"bin": "cli.js" | ||
} | ||
``` | ||
##### cache | ||
Insert this hashbang at the beginning of **cli.js**: | ||
Type: `boolean`<br> | ||
Default: `true` | ||
```jsx | ||
#!/usr/bin/env NODE_NO_WARNINGS=1 node --loader=import-jsx | ||
Cache transpiled source code. | ||
const HelloWorld = () => <h1>Hello world</h1>; | ||
``` | ||
### importJsx.create([options]) | ||
### Disable cache | ||
Factory method to create a version of `importJsx()` with pre-defined options. | ||
Useful when you need a custom pragma, but don't want to pass it along with each `importJsx()` call. | ||
`import-jsx` caches transpiled sources by default, so that the same file is transpiled only once. | ||
If that's not a desired behavior, turn off caching by setting `IMPORT_JSX_CACHE=0` or `IMPORT_JSX_CACHE=false` environment variable. | ||
#### options | ||
Type: `object` | ||
Options to pass to `importJsx()`. | ||
```js | ||
// Before | ||
const importJsx = require('import-jsx'); | ||
importJsx('./a', {pragma: 'x'}); | ||
importJsx('./b', {pragma: 'x'}); | ||
// After | ||
const importJsx = require('import-jsx').create({pragma: 'x'}); | ||
importJsx('./a'); | ||
importJsx('./b'); | ||
```console | ||
IMPORT_JSX_CACHE=0 node --loader=import-jsx my-code.js | ||
``` |
@@ -1,31 +0,26 @@ | ||
'use strict'; | ||
// Only load these if compiled source is not already cached | ||
let babel; | ||
let jsxTransform; | ||
let reactPreset; | ||
const transform = (source, options, modulePath) => { | ||
const transform = async (source, filename) => { | ||
if (!babel) { | ||
babel = require('@babel/core'); | ||
jsxTransform = require('@babel/plugin-transform-react-jsx'); | ||
babel = await import('@babel/core'); | ||
reactPreset = await import('@babel/preset-react'); | ||
} | ||
if (source.includes('React')) { | ||
options.pragma = 'React.createElement'; | ||
options.pragmaFrag = 'React.Fragment'; | ||
} | ||
const plugins = [ | ||
const presets = [ | ||
[ | ||
jsxTransform, | ||
reactPreset.default, | ||
{ | ||
pragma: options.pragma, | ||
pragmaFrag: options.pragmaFrag, | ||
useBuiltIns: true | ||
runtime: 'automatic', | ||
pure: false, | ||
useBuiltIns: true, | ||
useSpread: true | ||
} | ||
] | ||
].filter(Boolean); | ||
]; | ||
const result = babel.transformSync(source, { | ||
plugins, | ||
filename: modulePath, | ||
const result = await babel.transformAsync(source, { | ||
presets, | ||
filename, | ||
sourceMaps: 'inline', | ||
@@ -39,2 +34,2 @@ babelrc: false, | ||
module.exports = transform; | ||
export default transform; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
4
Yes
7723
10
120
87
1
+ Added@babel/preset-react@^7.18.6
+ Added@babel/plugin-transform-react-display-name@7.25.9(transitive)
+ Added@babel/plugin-transform-react-jsx-development@7.25.9(transitive)
+ Added@babel/plugin-transform-react-pure-annotations@7.25.9(transitive)
+ Added@babel/preset-react@7.25.9(transitive)
+ Addedcommon-path-prefix@3.0.0(transitive)
+ Addedfind-cache-dir@4.0.0(transitive)
+ Addedfind-up@6.3.0(transitive)
+ Addedlocate-path@7.2.0(transitive)
+ Addedp-limit@4.0.0(transitive)
+ Addedp-locate@6.0.0(transitive)
+ Addedpath-exists@5.0.0(transitive)
+ Addedpkg-dir@7.0.0(transitive)
+ Addedyocto-queue@1.1.1(transitive)
- Removedcaller-path@^3.0.1
- Removedresolve-from@^3.0.0
- Removedrimraf@^3.0.0
- Removed@babel/plugin-proposal-object-rest-spread@7.20.7(transitive)
- Removed@babel/plugin-syntax-object-rest-spread@7.8.3(transitive)
- Removed@babel/plugin-transform-destructuring@7.25.9(transitive)
- Removed@babel/plugin-transform-parameters@7.25.9(transitive)
- Removedbalanced-match@1.0.2(transitive)
- Removedbrace-expansion@1.1.11(transitive)
- Removedcaller-callsite@4.1.0(transitive)
- Removedcaller-path@3.0.1(transitive)
- Removedcallsites@3.1.0(transitive)
- Removedcommondir@1.0.1(transitive)
- Removedconcat-map@0.0.1(transitive)
- Removedfind-cache-dir@3.3.2(transitive)
- Removedfind-up@4.1.0(transitive)
- Removedfs.realpath@1.0.0(transitive)
- Removedglob@7.2.3(transitive)
- Removedinflight@1.0.6(transitive)
- Removedinherits@2.0.4(transitive)
- Removedlocate-path@5.0.0(transitive)
- Removedminimatch@3.1.2(transitive)
- Removedonce@1.4.0(transitive)
- Removedp-limit@2.3.0(transitive)
- Removedp-locate@4.1.0(transitive)
- Removedp-try@2.2.0(transitive)
- Removedpath-exists@4.0.0(transitive)
- Removedpath-is-absolute@1.0.1(transitive)
- Removedpkg-dir@4.2.0(transitive)
- Removedresolve-from@3.0.0(transitive)
- Removedrimraf@3.0.2(transitive)
- Removedwrappy@1.0.2(transitive)
Updated@babel/core@^7.21.0
Updatedfind-cache-dir@^4.0.0
Updatedmake-dir@^3.1.0