Skip to content

Lazily initialise values by deferring their creation until first use, resulting in better performance.

License

Notifications You must be signed in to change notification settings

reiss-d/lazy-init

Repository files navigation

lazy-init

Lazily initialize values by deferring their creation until first use, resulting in better performance.

Now also bringing block expressions to JavaScript/TypeScript.

Index

Installation

This library requires your code is transpilied with any of the following:

If you require a version of next/swc unsupported by the plugin and it is listed here, create an issue requesting support.

Next.js

Version Plugin
>= v13.4.20-canary.32 @lazy-init/plugin-swc-v83
>= v13.4.10-canary.1 @lazy-init/plugin-swc-v81
# using npm
npm install lazy-init && npm install --save-dev @lazy-init/plugin-swc-{{version}}
# using pnpm
pnpm add lazy-init && pnpm add -D @lazy-init/plugin-swc-{{version}}

Add the following to your next config file:

next.config.js

module.exports = {
   experimental: {
      swcPlugins: [
         // empty config object `{}` is required.
         [require.resolve('@lazy-init/plugin-swc-{{version}}'), {}],
      ],
   },
}

next.config.mjs (ESM)

import { createRequire } from 'node:module'
const require = createRequire(import.meta.url)

export default {
   experimental: {
      swcPlugins: [
         // empty config object `{}` is required.
         [require.resolve('@lazy-init/plugin-swc-{{version}}'), {}],
      ],
   },
}

SWC - @swc/core

Version Supported
>= 1.3.81 @lazy-init/plugin-swc-v83
>= 1.3.68 @lazy-init/plugin-swc-v81
# using npm
npm install lazy-init && npm install --save-dev @lazy-init/plugin-swc-{{version}} 
# using pnpm
pnpm add lazy-init && pnpm add -D @lazy-init/plugin-swc-{{version}}

The empty config object {} is required.

// .swcrc
{
   "jsc": {
      "experimental": {
         "plugins": [
            ["@lazy-init/plugin-swc-{{version}}", {}]
         ]
      }
   }
}

esbuild

Version Supported
0.18.x || 0.19.x @lazy-init/esbuild-plugin
# using npm
npm install lazy-init && npm install --save-dev @lazy-init/esbuild-plugin
# using pnpm
pnpm add lazy-init && pnpm add -D @lazy-init/esbuild-plugin

The include and exclude properties are glob arrays which follow the same behaviour as include and exclude in Typescripts tsconfig.json.

These options are not required. However, providing either will improve performance.

By default, imports from node_modules will be skipped by this plugin unless excludeNodeModules is set to false.

// your build file
const { lazyInitPlugin } = require('@lazy-init/esbuild-plugin')
const esbuild = require('esbuild')

esbuild.build({
   // ... other options
   plugins: [
      // If you are using plugins that transform paths, place them first.
      lazyInitPlugin({
         include: ['src'],
         exclude: ['src/**/*.test.ts'],
         excludeNodeModules: true, // default
      }),
   ],
})

tsup

tsup uses esbuild internally, therefore everything documented in the esbuild section applies here. The only difference is a slight change in the configuration.

Version Supported
>= 7.x.x @lazy-init/esbuild-plugin

Note: just copy the include and exclude arrays from your tsconfig.json.

// tsup.config.ts
import { lazyInitPlugin } from '@lazy-init/esbuild-plugin'
import { defineConfig } from 'tsup'

export default defineConfig({
   // ... other options
   esbuildPlugins: [
      // If you are using plugins that transform paths, place them first.
      lazyInitPlugin({
         include: ['src'],
         exclude: ['src/**/*.test.ts'],
         excludeNodeModules: true, // default
      }),
   ],
})

eslint

This plugin is only necessary if you want to use the lz.async method while using @typescript-eslint with a configuration that extends rules which require type checking.

See eslint-plugin-lazy-init.

Basic Usage

For more in-depth examples, see the per method documentation.

import { lz, lzc } from 'lazy-init' // ESM
const { lz, lzc } = require('lazy-init') // Common JS

// call `lz` for non-primitive values
lz({ foo: 1 })
lz([1, 2, 3])
lz(new Map([['key', 'value']]))

// call `lz.fn` for sync functions
lz.fn(() => {})

// call `lz.async` for async functions
lz.async(async () => {})

// call `lzc` to cache by default
const first = lzc({ a: 'foo' })
const second = lzc({ a: 'foo' })

console.log(first === second) // true

Methods

Click the method to see its documentation:

Caching

Caching results in only a single value ever being created for the given value structure. This can improve performance and reduce memory usage.

Caching can be enabled by setting the cache property to true on a options object or by using the lzc method where caching is enabled by default.

// using `lz`
lz({}) // not cached
lz({}, { cache: true }) // cached

// using `lzc`
lzc({}) // cached
lzc({}, { cache: false }) // not cached

When caching is enabled, the value will also be frozen unless you explicitly say otherwise. This is because caching an object that is not frozen is dangerous.

The object may mistakenly be mutated by the user, yet other recipients of this cached object do not expect it to change.

// using `lz`
lz({}) // N/A
lz({}, { cache: true, freeze: false }) // cached
lz({}, { cache: true }) // cached & frozen

// using `lzc`
lzc({}) // cached & frozen
lzc({}, { freeze: false }) // cached
lzc({}, { cache: false }) // N/A

Referentially comparing cached and non-cached values:

// `cfoo` and `cbar` share the same structure and are both
// cached, therefore they are the same object.
const cfoo = lzc({ a: 1 })
const cbar = lzc({ a: 1 })
cfoo === cbar // true
// `cfoo` and `buzz` share the same structure, however, `buzz`
//  is not cached, therefore they are different objects.
const buzz = lzc({ a: 1 }, { cache: false })
cfoo === buzz // false
// `cfoo` and `cdiff` are cached, however, they do not share the
// same structure and are therefore different objects.
const cdiff = lzc({ a: 5 })
cfoo === cdiff // false

There are separate caches for frozen and non-frozen objects. Therefore, frozen and non-frozen objects with the same structure will not be the same object.

const cfoo = lzc({ a: 1 })
const cbar = lzc({ a: 1 }, { freeze: false })
cfoo === cbar // false

Freezing

By default, freezing a value will perform a deep freeze on it.

To change this behaviour, set the environment variable LAZY_INIT_FREEZE_MODE to one of the following values:

  • "deep" (default)
  • "shallow"
  • "none"

Deep Freeze

The values of each key and symbol property will be recursively frozen. However, this only applies to arrays and plain objects. Other objects such as Set and Map will not be frozen.

const foo = lz({
   val: 'bar',
   obj: { a: 0, b: [], c: new Set() },
}, true)
foo.val = 'buzz' // error
foo.obj.a = 2 // error
foo.obj.b.push(1) // error
foo.obj.c.add(1) // ok
foo.obj.c = null // error

Shallow Freeze

Only the value itself will be frozen, not any of its array/object properties.

const foo = lz({
   val: 'bar',
   obj: { a: 0 },
}, true)
foo.val = 'buzz' // error
foo.obj.a = 2 // ok
foo.obj = {} // error

None

The value will not be frozen.

License

This repository is licensed under the MIT License found here. Each package/crate may contain a LICENSE file in its root which takes precedence and may include or depend on third-party code with its own licensing conditions.


About

Lazily initialise values by deferring their creation until first use, resulting in better performance.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published