Как я могу подавить импорт React в выводе TypeScript, чтобы разрешить ссылки CDN?

Я пытаюсь использовать URL-адреса CDN для ссылки на библиотеки реагирования и реагирования в моем машинописном приложении. Я не хочу объединять их в свой скомпилированный файл .js, поскольку меня волнует производительность в производстве. Но, похоже, нет простого способа добиться этого без ручного редактирования скомпилированного файла .js для удаления ссылок на импорт. Посмотрите этот минимальный репродукция (https://jsfiddle.net/grm6ze1b/):

тест.html:

<!DOCTYPE html>
<div id = "app">
</div>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.min.js"></script>
<script src = "test.js" type = "module"></script>

тест.ц:

import * as React from 'react';
import * as ReactDOM from 'react-dom/client';

class HelloComponent extends React.Component {
    render() {
        return React.createElement("div", null, "Hello, World!");
    }
}

document.addEventListener("DOMContentLoaded", () => {
    const oReactRoot = ReactDOM.createRoot(document.getElementById("app")!);
    oReactRoot.render(React.createElement(HelloComponent, null));
});

tsconfig.json:

{
    "compilerOptions": {
        "target": "es2020",
        "moduleResolution": "node",
        "strict": true,
        "noEmitOnError": true
    },
    "include": [
        "**/*.ts"
    ]
}

Во-первых, я получил сообщение «Uncaught SyntaxError: объявления импорта могут появляться только на верхнем уровне модуля» при загрузке страницы в браузере. Хорошо, я исправил это, добавив type = "module" в тег скрипта для test.js.

Затем я получил сообщение «Uncaught TypeError: спецификатор «реагировать» был пустым спецификатором, но ни на что не был переназначен. Спецификаторы относительных модулей должны начинаться с «./», «../» или «/».'. Вот где я застрял. Я могу решить эту проблему, вручную закомментировав две верхние строки import в скомпилированном файле test.js. Но это кажется абсурдным, поскольку мне придется делать это каждый раз, когда я запускаю tsc, чтобы перекомпилировать свой .ts-файл.

Я изучил документацию https://www.typescriptlang.org/tsconfig/ и обнаружил, что существуют различные параметры, управляющие обработкой импорта и тем, появляются ли они в скомпилированном JS.At https://www .typescriptlang.org/tsconfig/#verbatimModuleSyntax там написано:

По умолчанию TypeScript делает то, что называется импортом. В принципе, если вы напишете что-то вроде

импортировать { Автомобиль } из "./car"; функция экспорта привод(автомобиль: Автомобиль) { // ... }

TypeScript обнаруживает, что вы используете импорт только типов и полностью отменяет импорт.

Но со мной этого не происходит. Мой скомпилированный файл test.js по-прежнему содержит импорт, хотя я импортирую только типы React, а не всю библиотеку:

PS C:\inetpub\wwwroot\TypeScriptReactTest> npm ls --all
TypeScriptReactTest@ C:\inetpub\wwwroot\TypeScriptReactTest
+-- @types/[email protected]
| `-- @types/[email protected] deduped
`-- @types/[email protected]
  +-- @types/[email protected]
  `-- [email protected]

Третье решение, которое я попробовал, — изменить импорт на ссылочные директивы:

///<reference types = "react"/>
///<reference types = "react-dom/client"/>

Но это привело к ошибке TS2339: свойство createRoot не существует для типа typeof import("C:/inetpub/wwwroot/TypeScriptReactTest/node_modules/@types/react-dom/index")». (Та же ошибка, если бы я вместо этого попробовал reference paths = "".)

Наконец, я попытался определить карту импорта, чтобы сообщить браузеру, где найти импортируемые модули:

<script type = "importmap">
 {
    "imports": {
      "react": "https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js",
      "react-dom/client": "https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.min.js"
    }
  }
</script>

Это привело к новой ошибке: «Uncaught TypeError: наследие класса React.Component не является объектом или нулевым значением».

Вот я и бьюсь головой о кирпичную стену и буду благодарен за любую помощь. Я очень надеюсь, что ответ не в том, что мне нужно использовать что-то вроде https://webpack.js.org/configuration/externals/, чтобы справиться с этим. В моем полноценном рабочем приложении (не в этом репродукции) используется пакет ASP.NET Core WebOptimizer, и мне бы очень не хотелось добавлять еще один шаг в мою цепочку инструментов только для обработки того, что, как я полагаю, является довольно простым и распространенным вариантом использования.

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
3
0
112
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

React не был разработан для использования стандартными модулями JavaScript (ESM) и еще не был реорганизован для их встроенной поддержки (ссылка: Проблема отслеживания GitHub) на момент написания этого ответа. До тех пор, пока он не станет изначально поддерживать ESM, вы должны либо

  1. полагаться на какой-либо инструмент для преобразования кода CommonJS, опубликованного React, например, на сборщик или сторонний CDN, который преобразуется в ESM, или

  2. напишите свой код, чтобы использовать глобальные переменные, предоставленные модулями UMD, которые вы показали в вопросе.

Судя по деталям вашего вопроса, похоже, что вы хотите избежать дополнительных локальных инструментов, таких как упаковщик… и хотя можно полагаться на (выходные данные) ESM CDN, возможно, было бы предпочтительнее просто реорганизовать ваши операторы импорта и импортировать карту. Ниже я опишу, как это сделать.

Проблема

Каждый модуль UMD, на который вы указали ссылку в вопросе, имеет глобальную переменную. Модуль React UMD создает объект с именем React, а модуль React-dom UMD создает объект с именем ReactDOM. Однако соответствующие объявления TypeScript для этих модулей ( @types/react , @types/react-dom) не используют эти глобальные переменные.

Импортировать карты

Вы можете определить записи карты импорта для сопоставления простых спецификаторов с модулями, которые экспортируют соответствующую глобальную переменную в качестве экспорта по умолчанию . Этот шаблон поддерживается объявлениями типов для react и react-dom. Например:

react_umd.js:

export default globalThis.React;

react_dom_umd.js:

export default globalThis.ReactDOM;

импортировать карту:

{
  "imports": {
    "react": "./react_umd.js",
    "react-dom/client": "./react_dom_umd.js"
  }
}

Операторы импорта

Тогда вместо использования оператора импорта пространства имен для каждого пакета...

import * as React from "react";
import * as ReactDOM from "react-dom/client";

…импортируйте каждое пространство имен из стандартного экспорта :

import { default as React } from "react";
import { default as ReactDOM } from "react-dom/client";

Использование этого шаблона также поддерживается при объединении, поэтому, если вы решите объединить свой код в будущем, вам не придется реорганизовать этот синтаксис.

Это также может потребовать от вас обновить TSConfig, чтобы использовать опцию компилятора esModuleInterop , которая подразумевается при настройке модуля на NodeNext:

{
  "compilerOptions": {
    "strict": true,
    "target": "ES2020",
    "module": "NodeNext",
    "noEmitOnError": true
  },
  "include": ["**/*.ts"]
}

Обязательно перекомпилируйте с помощью tsc!

HTML

Используйте обновленную карту импорта в своем HTML. Обязательно закажите его перед скриптом модуля:

<script src = "https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.development.js" integrity = "sha512-YFI6ChaPQ5hH9o8Q4n5ZzDHrhrwZ3dhgZSQ2JC/pgmYuD0QtG0iwQgfFa1J+o4jvklsKBupcHz5Tx1yqa25FFQ= = " crossorigin = "anonymous" referrerpolicy = "no-referrer"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.development.min.js" integrity = "sha512-aTIGujEp0xIZSXrXXFjXL3YiozmMRYjKmll3rLYTmUIGaaidsrQNn+ii04E+VwlpIUNZOF3UBoXRmNQBEdD/qQ= = " crossorigin = "anonymous" referrerpolicy = "no-referrer"></script>

<script type = "importmap">
  {
    "imports": {
      "react": "./react_umd.js",
      "react-dom/client": "./react_dom_umd.js"
    }
  }
</script>

<script type = "module" src = "test.js"></script>

<div id = "app"></div>

Воспроизведение во фрагменте кода

Для воспроизводимого примера в одном файле, который выполняется в этом ответе, я преобразую два модуля выше (react_umd.js и react_dom_umd.js) в URL-адреса данных и встрою содержимое test.js (после компиляции test.ts):

<script src = "https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.development.js" integrity = "sha512-YFI6ChaPQ5hH9o8Q4n5ZzDHrhrwZ3dhgZSQ2JC/pgmYuD0QtG0iwQgfFa1J+o4jvklsKBupcHz5Tx1yqa25FFQ= = " crossorigin = "anonymous" referrerpolicy = "no-referrer"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.development.min.js" integrity = "sha512-aTIGujEp0xIZSXrXXFjXL3YiozmMRYjKmll3rLYTmUIGaaidsrQNn+ii04E+VwlpIUNZOF3UBoXRmNQBEdD/qQ= = " crossorigin = "anonymous" referrerpolicy = "no-referrer"></script>

<script type = "importmap">
  {
    "imports": {
      "react": "data:text/javascript;base64,ZXhwb3J0IGRlZmF1bHQgZ2xvYmFsVGhpcy5SZWFjdDs = ",
      "react-dom/client": "data:text/javascript;base64,ZXhwb3J0IGRlZmF1bHQgZ2xvYmFsVGhpcy5SZWFjdERPTTs = "
    }
  }
</script>

<!-- This is the inlined content of "test.js" after compiling with tsc -->
<script type = "module">
import { default as React } from "react";
import { default as ReactDOM } from "react-dom/client";
class HelloComponent extends React.Component {
    render() {
        return React.createElement("div", null, "Hello, World!");
    }
}
document.addEventListener("DOMContentLoaded", () => {
    const oReactRoot = ReactDOM.createRoot(document.getElementById("app"));
    oReactRoot.render(React.createElement(HelloComponent, null));
});
</script>

<div id = "app"></div>

Ух ты, это потрясающий ответ, очень подробный и подробно объясняющий проблему. Итак, для моего образования: globalThis.React функционирует так же, как window.React, и своего рода экспортирует модуль UMD React в значение модуля ESM, которое может быть совместимо с операторами импорта, которые TypeScript сохраняет в моем скомпилированном файле .js. Я правильно это понимаю? Кстати, мне пришлось добавить "allowSyntheticDefaultImports": true в мой tsconfig.json, потому что я использовал "moduleResolution": "node" вместо "moduleResolution": "NodeNext". Не уверен, что разрешение модуля имеет для меня большое значение...

Jordan Rieger 06.07.2024 21:09
^ @JordanRieger Да, верно — в контексте окна браузера globalThis и window относятся к одному и тому же глобальному объекту.
jsejcksn 06.07.2024 22:06

Другие вопросы по теме