Node.js의 기본 ESM w/require() 폴백 및 모든 프런트 엔드 컴파일러 지원!

기본 ESM 지원은 몇 달 전에 Node.js CURRENT 및 LTS에서 플래그가 지정되지 않았습니다. 다이빙을 시작하고 나니 예상했던 것보다 조금 더 어려웠습니다.

제가 걱정했던 한 가지는 프런트 엔드 컴파일러가 ESM을 해석하는 방식과 Node.js가 해석하는 방식 사이의 차이점을 탐색하는 것이었습니다. 브라우저, ESM의 진입점을 분할하려면 모두 동일한 package.json 속성을 이해해야 합니다.






미칼 로저스










컴파일러 전체에서 package.json의 새로운 조건부 `내보내기` 맵이 얼마나 광범위하게 지원됩니까?/ccnodejs.org/dist/latest-v1…


오후 18:24 - 2020년 6월 8일





2

31



그 대답은 "아니오!"였습니다. 컴파일러는 아직 Node.js의 내보내기 맵을 이해하지 못합니다.

라이브러리 소비자가 require()를 사용하여 라이브러리를 가져올 수 있도록 하려면 내보내기 맵을 사용해야 하며 이 매핑은 Node.js에서 사용되지만 컴파일러에는 표시되지 않습니다.

이것은 몇 가지를 의미합니다.
  • 어디에서나 기본적으로 ESM을 사용하려면 package.json에 { “type”: “module” }를 설정하고 싶을 것입니다. 이렇게 하면 Node.js가 프로젝트의 .js 파일을 ESM으로 해석하고 컴파일러는 이미 소스 파일에서 ESM을 감지할 수 있습니다. 동일한 구현의 별도 소스 파일을 유지하고 싶지 않은 한 .mjs를 사용하는 것은 실제로 이점이 없으며 아마도 그렇지 않을 것입니다.
  • 올바른 파일 경로가 아니고 이 매핑이 컴파일러에 표시되지 않기 때문에 import main from ‘packageName/defaults’와 같은 것을 허용하는 내보내기 맵을 의도한 대로 사용할 수 없습니다.







  • 미칼 로저스










    내가 뭔가를 놓치고 있는 건가요, 아니면 Node.js에서 { type: module } w/{ type: module }로 ESM으로 작성된 모듈/파일을 require()할 수 있는 방법이 *없*나요? cjs에서 구현한 다음 ESM 진입점을 노출합니다.


    오후 14:48 - 2020년 6월 19일





    0

    14


    import를 사용하여 이전 모듈 표준으로 작성된 Node.js 모듈을 로드할 수 있지만 ESM 모듈은 로드할 수 없으므로require() 호환성이 한 방향으로만 흐릅니다.
    require()를 지원하려면 문자 그대로 이전 모듈 형식으로 작성되고 내보내기 맵에서 ESM 파일에 대해 오버레이되는 별도의 소스 파일이 있어야 합니다.

    다음은 내보내기가 많은 js-multiformats의 예입니다.

     "exports": {
        ".": {
          "import": "./index.js",
          "require": "./dist/index.cjs"
        },
        "./basics.js": {
          "import": "./basics.js",
          "require": "./dist/basics.cjs"
        },
        "./bytes.js": {
          "import": "./bytes.js",
          "require": "./dist/bytes.cjs"
        },
        "./cid.js": {
          "import": "./cid.js",
          "require": "./dist/cid.cjs"
        },
        ...
    }
    

    롤업으로 컴파일하는 것은 @mylesborins가 올바른 방향을 알려준 후 매우 간단했지만 조금 더 필요했습니다.

    다음은 js-multiformats의 또 다른 예입니다.

    import globby from 'globby'
    import path from 'path'
    
    let configs = []
    
    const _filter = p => !p.includes('/_') && !p.includes('rollup.config')
    
    const relativeToMain = name => ({
      name: 'relative-to-main',
      renderChunk: source => {
        while (source.includes("require('../index.js')")) {
          source = source.replace("require('../index.js')", "require('multiformats')")
        }
        while (source.includes("require('../")) {
          source = source.replace('require(\'../', 'require(\'multiformats/')
        }
        return source
      }
    })
    
    const plugins = [relativeToMain('multiformats')]
    const add = (pattern) => {
      configs = configs.concat(globby.sync(pattern).filter(_filter).map(inputFile => ({
        input: inputFile,
        output: {
          plugins: pattern.startsWith('test') ? plugins : null,
          file: path.join('dist', inputFile).replace('.js', '.cjs'),
          format: 'cjs'
        }
      })))
    }
    add('*.js')
    add('bases/*.js')
    add('hashes/*.js')
    add('codecs/*.js')
    add('test/*.js')
    add('test/fixtures/*.js')
    console.log(configs)
    
    export default configs
    

    모든 .js 파일과 모든 테스트를 컴파일하려고 합니다. 이 번역에는 잘못될 수 있는 부분이 많으므로 require()를 사용하는 각 테스트 버전을 컴파일하는 것이 매우 유용합니다. 또한 내보낸 인터페이스가 각 진입점에 대해 동일하게 유지되도록 합니다.

    또한 테스트에서 상대적 가져오기를 컴파일하고 대신 로컬 패키지 이름을 사용해야 합니다. Node.js는 로컬 패키지 이름을 올바르게 해석하지만 상대 가져오기를 사용하는 경우 실제로 내보내기 맵을 완전히 건너뛰고 실패합니다.

    상대 가져오기에서 테스트를 마이그레이션하는 것은 유혹적일 수 있지만 컴파일러는 종종 Node.js가 수행하는 방식으로 로컬 패키지 이름에 대한 조회를 지원하지 않으므로 지원하지 않습니다.

    좋은 웹페이지 즐겨찾기