Я пытаюсь использовать 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, и мне бы очень не хотелось добавлять еще один шаг в мою цепочку инструментов только для обработки того, что, как я полагаю, является довольно простым и распространенным вариантом использования.
React не был разработан для использования стандартными модулями JavaScript (ESM) и еще не был реорганизован для их встроенной поддержки (ссылка: Проблема отслеживания GitHub) на момент написания этого ответа. До тех пор, пока он не станет изначально поддерживать ESM, вы должны либо
полагаться на какой-либо инструмент для преобразования кода CommonJS, опубликованного React, например, на сборщик или сторонний CDN, который преобразуется в ESM, или
напишите свой код, чтобы использовать глобальные переменные, предоставленные модулями 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. Обязательно закажите его перед скриптом модуля:
<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>
window
относятся к одному и тому же глобальному объекту.
Ух ты, это потрясающий ответ, очень подробный и подробно объясняющий проблему. Итак, для моего образования:
globalThis.React
функционирует так же, какwindow.React
, и своего рода экспортирует модуль UMD React в значение модуля ESM, которое может быть совместимо с операторами импорта, которые TypeScript сохраняет в моем скомпилированном файле .js. Я правильно это понимаю? Кстати, мне пришлось добавить"allowSyntheticDefaultImports": true
в мой tsconfig.json, потому что я использовал"moduleResolution": "node"
вместо"moduleResolution": "NodeNext"
. Не уверен, что разрешение модуля имеет для меня большое значение...