nodejs/브라우저와 호환되는 라이브러리 작성
질문:
플랫폼별 기능을 사용하면 호환성 문제가 발생할 수 있으며 다음과 같은 상황이 발생할 수 있다
fetch/FormData
Blob
와nodejs의Buffer
다른 모듈 사양
이것은 매우 평범한 일이다.현재 cjs/amd/ife/umd/esm를 포함한 여러 가지 규범이 있기 때문에 이들을 지원하는 것(또는 적어도 주류 cjs/esm을 지원하는 것)도 필수적이다.다행히도, 패키지 도구 롤러는 서로 다른 형식의 출력 파일을 지원하기 위해 상응하는 설정을 제공합니다.
형상상
// rollup.config.js
export default defineConfig({
input: 'src/index.ts',
output: [
{ format: 'cjs', file: 'dist/index.js', sourcemap: true },
{ format: 'esm', file: 'dist/index.esm.js', sourcemap: true },
],
plugins: [typescript()],
})
그런 다음 패키지에 지정합니다.json{
"main": "dist/index.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts"
}
Many libraries support cjs/esm, such as rollup, but there are also libraries that only support esm, such as unified.js series
플랫폼 유한 코드
- 서로 다른 포털 파일을 통해 다른 내보내기 파일을 패키지화하고
browser
환경과 관련된 코드를 지정합니다(예: dist/browser.js
/dist/node.js
: 패키지 도구를 사용할 때 주의해야 합니다(비용은 사용자에게 이전).- 코드를 사용하여 운영 환경의 동적 로드 확인
비교
다른 출구
코드 판단
우세하다
더욱 철저한 코드 격리
패키지 도구에 의존하지 않는 행위
최종 코드는 현재 환경의 코드만 포함합니다
결점
패키지 도구의 사용자 행동에 따라 다름
환경을 판단하는 코드가 정확하지 않을 수 있습니다
최종 코드는 모든 코드를 포함하지만 선택적으로 불러옵니다
axios combines the above two methods to achieve browser and nodejs support, but at the same time it leads to the shortcomings of the two methods and a little confusing behavior. Refer to getDefaultAdapter. For example, in the jsdom environment, it will be considered as a browser environment, please refer to detect jest and use http adapter instead of XMLHTTPRequest
다른 입력 파일을 통해 다른 내보내기 파일을 포장합니다
// rollup.config.js
export default defineConfig({
input: ['src/index.ts', 'src/browser.ts'],
output: [
{ dir: 'dist/cjs', format: 'cjs', sourcemap: true },
{ dir: 'dist/esm', format: 'esm', sourcemap: true },
],
plugins: [typescript()],
})
{
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"types": "dist/index.d.ts",
"browser": {
"dist/cjs/index.js": "dist/cjs/browser.js",
"dist/esm/index.js": "dist/esm/browser.js"
}
}
코드를 사용하여 실행 중인 환경의 동적 불러오기를 확인합니다
기본적으로 코드에서 판단한 다음
await import
import { BaseAdapter } from './adapters/BaseAdapter'
import { Class } from 'type-fest'
export class Adapter implements BaseAdapter {
private adapter?: BaseAdapter
private async init() {
if (this.adapter) {
return
}
let Adapter: Class<BaseAdapter>
if (typeof fetch === 'undefined') {
Adapter = (await import('./adapters/NodeAdapter')).NodeAdapter
} else {
Adapter = (await import('./adapters/BrowserAdapter')).BrowserAdapter
}
this.adapter = new Adapter()
}
async get<T>(url: string): Promise<T> {
await this.init()
return this.adapter!.get(url)
}
}
// rollup.config.js
export default defineConfig({
input: 'src/index.ts',
output: { dir: 'dist', format: 'cjs', sourcemap: true },
plugins: [typescript()],
})
Note: vitejs cannot bundle this kind of package, because the nodejs native package does not exist in the browser environment, this is a known error, refer to: Cannot use amplify-js in browser environment (breaking vite/snowpack/esbuild).
플랫폼별 종속성
import
을 의존항으로 한다. 이것은 서로 다른 환경에서 폭발할 것이다(예를 들어 node-fetch
는 브라우저에서 폭발할 것이다)require
를 통해 동적 의존항을 도입한다. 이로써 코드 세그먼트를 나누고 의존항은 선택적으로 단독 파일로 불러온다import()
/dist/browser.js
: 사용 시 주의해야 한다dist/node.js
옵션 의존항을 사용자가 직접 작성할 수 있도록 함: 사용 시 주의(비용을 사용자에게 전달)요구 사항
수입하다
가슴에 넣을까요?
예, 그렇습니다.
아니오.
개발자가 주의해야 합니까
아니오.
아니오.
여러 번 불러올까요?
아니오.
예, 그렇습니다.
동기화
예, 그렇습니다.
아니오.
요약 지원
맞다
맞다
실행 중인 코드를 확인하는 중peerDependencies를 통해 의존항을 동적으로 도입합니다
// src/adapters/BaseAdapter.ts
import { BaseAdapter } from './BaseAdapter'
export class BrowserAdapter implements BaseAdapter {
private static init() {
if (typeof fetch === 'undefined') {
const globalVar: any =
(typeof globalThis !== 'undefined' && globalThis) ||
(typeof self !== 'undefined' && self) ||
(typeof global !== 'undefined' && global) ||
{}
// The key is the dynamic require here
Reflect.set(globalVar, 'fetch', require('node-fetch').default)
}
}
async get<T>(url: string): Promise<T> {
BrowserAdapter.init()
return (await fetch(url)).json()
}
}
실행 중인 코드에서 Require를 통해 의존항을 동적으로 도입합니다
// src/adapters/BaseAdapter.ts
import { BaseAdapter } from './BaseAdapter'
export class BrowserAdapter implements BaseAdapter {
// Note that this has become an asynchronous function
private static async init() {
if (typeof fetch === 'undefined') {
const globalVar: any =
(typeof globalThis !== 'undefined' && globalThis) ||
(typeof self !== 'undefined' && self) ||
(typeof global !== 'undefined' && global) ||
{}
Reflect.set(globalVar, 'fetch', (await import('node-fetch')).default)
}
}
async get<T>(url: string): Promise<T> {
await BrowserAdapter.init()
return (await fetch(url)).json()
}
}
패키지 결과몇몇 하위 문제에 부딪히다
typeof fetch === 'undefined'
const globalVar: any =
(typeof globalThis !== 'undefined' && globalThis) ||
(typeof self !== 'undefined' && self) ||
(typeof global !== 'undefined' && global) ||
{}
import()
:axios는 주로 판단TypeError: Right-hand side of'instanceof' is not callable
하고 FormData
는 기본적으로 내보내기 때문에 사용해야 한다form-data
(나 세대는 항상 내가 구멍을 파고 있다고 느낀다)내연 = > 개요
// inline
export default {
output: {
file: 'dist/extension.js',
format: 'cjs',
sourcemap: true,
},
}
// Outreach
export default {
output: {
dir: 'dist',
format: 'cjs',
sourcemap: true,
},
}
플랫폼 제한 유형 정의
다음 해결 방안은 본질적으로 여러 개의 패키지이다
(await import('form-data' )).default
/module/node
를 통해 서로 다른 기능을 탑재한다(사실 이것은 플러그인 시스템에 매우 가깝고 여러 모듈을 분리하는지 여부에 불과하다)다중 유형 정의 파일
블렌드 유형 정의
다중 모듈
우세하다
더욱 뚜렷한 환경 표지
통합 입구
더욱 뚜렷한 환경 표지
결점
사용자가 선택해야 함
유형 정의 이중화
사용자가 선택해야 함
불필요한 의존
유지 보수가 비교적 번거롭다(특히 유지 보수가 외롭지 않을 때)
다른 내보내기 파일 및 유형 정의를 패키지화하고 원하는 파일을 직접 지정해야 합니다.
그것은 주로 핵심 코드에서 추상적인 것을 한 층 추출한 다음에 플랫폼에 특정된 코드를 추출하여 단독으로 포장하는 것이다.
// src/index.ts
import { BaseAdapter } from './adapters/BaseAdapter'
export class Adapter<T> implements BaseAdapter<T> {
upload: BaseAdapter<T>['upload']
constructor(private base: BaseAdapter<T>) {
this.upload = this.base.upload
}
}
// rollup.config.js
export default defineConfig([
{
input: 'src/index.ts',
output: [
{ dir: 'dist/cjs', format: 'cjs', sourcemap: true },
{ dir: 'dist/esm', format: 'esm', sourcemap: true },
],
plugins: [typescript()],
},
{
input: ['src/adapters/BrowserAdapter.ts', 'src/adapters/NodeAdapter.ts'],
output: [
{ dir: 'dist/cjs/adapters', format: 'cjs', sourcemap: true },
{ dir: 'dist/esm/adapters', format: 'esm', sourcemap: true },
],
plugins: [typescript()],
},
])
사용자 예import { Adapter } from 'platform-specific-type-definition-multiple-bundle'
import { BrowserAdapter } from 'platform-specific-type-definition-multiple-bundle/dist/esm/adapters/BrowserAdapter'
export async function browser() {
const adapter = new Adapter(new BrowserAdapter())
console.log('browser:', await adapter.upload(new Blob()))
}
// import {NodeAdapter} from 'platform-specific-type-definition-multiple-bundle/dist/esm/adapters/NodeAdapter'
// export async function node() {
// const adapter = new Adapter(new NodeAdapter())
// console.log('node:', await adapter.upload(new Buffer(10)))
//}
플러그인 시스템을 사용하여 서로 다른 환경의 적응 코드를 여러 개의 서브 모듈로 분리하다
간단하게 말하자면, 실행할 때 의존 관계를 서로 다른 하위 모듈 (예: 위
module/browser
으로 확장하거나, 플러그인 API가 매우 강하다면, 플러그인 하위 모듈에서 분리할 수 있는 공식 적응 코드를 사용할 수 있습니다.
Reference
이 문제에 관하여(nodejs/브라우저와 호환되는 라이브러리 작성), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/rxliuli/write-nodejs-browser-compatible-libraries-1d68텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)