Использование модуля общего узла для общих классов - PullRequest
8 голосов
/ 30 октября 2019

Цель

Итак, у меня есть проект с такой структурой:

  • ionic-app
  • firebase-functions
  • shared

Цель состоит в том, чтобы определить общие интерфейсы и классы в модуле shared.

Ограничения

Надеюсьне хочу загружать мой код в npm, чтобы использовать его локально, и я не планирую загружать код вообще. Он должен работать на 100% в автономном режиме.

В то время как процесс разработки должен работать в автономном режиме, модули ionic-app и firebase-functions будут развернуты в firebase (хостинг и функции). Поэтому там должен быть доступен код из модуля shared.

То, что я пробовал до сих пор

  • Я пытался использовать ProjectСсылки в машинописном тексте, но я не приблизил его к работе
  • Я пробовал установить его как модуль npm, как во втором ответе на этот вопрос
    • Поначалу кажется, что все работает нормально, но во время сборки я получаю такую ​​ошибку при запуске firebase deploy:
Function failed on loading user code. Error message: Code in file lib/index.js can't be loaded.
Did you list all required modules in the package.json dependencies?
Detailed stack trace: Error: Cannot find module 'shared'
    at Function.Module._resolveFilename (module.js:548:15)
    at Function.Module._load (module.js:475:25)
    at Module.require (module.js:597:17)
    at require (internal/module.js:11:18)
    at Object.<anonymous> (/srv/lib/index.js:5:18)

Вопрос

Есть ли у вас решение для создания общего модуля с использованием конфигурации с типами сценариев или NPM?

Пожалуйста, не отмечайте это как дубликат → Я пробовал любое решение, которое нашелon StackOverflow.

Дополнительная информация

Настройка для общего доступа:

// package.json
{
  "name": "shared",
  "version": "1.0.0",
  "description": "",
  "main": "dist/src/index.js",
  "types": "dist/src/index.d.ts",
  "files": [
    "dist/src/**/*"
  ],
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "publishConfig": {
    "access": "private"
  }
}

// tsconfig.json
{
  "compilerOptions": {
    "module": "commonjs",
    "rootDir": ".",
    "sourceRoot": "src",
    "outDir": "dist",
    "sourceMap": true,
    "declaration": true,
    "target": "es2017"
  }
}

Настройка для функций:

// package.json
{
  "name": "functions",
  "scripts": {
    "lint": "tslint --project tsconfig.json",
    "build": "tsc",
    "serve": "npm run build && firebase serve --only functions",
    "shell": "npm run build && firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
  "engines": {
    "node": "8"
  },
  "main": "lib/index.js",
  "dependencies": {
    "firebase-admin": "^8.0.0",
    "firebase-functions": "^3.1.0",
    "shared": "file:../../shared"
  },
  "devDependencies": {
    "@types/braintree": "^2.20.0",
    "tslint": "^5.12.0",
    "typescript": "^3.2.2"
  },
  "private": true
}


// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": "./",
    "module": "commonjs",
    "noImplicitReturns": true,
    "noUnusedLocals": false,
    "rootDir": "src",
    "outDir": "lib",
    "sourceMap": true,
    "strict": true,
    "target": "es2017"
  }
}

Текущий выпуск

Я добавил скрипт npm в общий модуль, который копирует все файлы (сhout index.js) для других модулей. Проблема в том, что я проверяю дубликаты кода в SCM, и мне нужно запускать эту команду при каждом изменении. Кроме того, IDE обрабатывает его как разные файлы.

Ответы [ 5 ]

1 голос
/ 15 ноября 2019

Предисловие: Я не слишком знаком с тем, как работает компиляция Typescript и как следует определять package.json в таком модуле. Хотя это решение и работает, его можно считать хакерским способом решения поставленной задачи.

Предполагая следующую структуру каталогов:

project/
  ionic-app/
    package.json
  functions/
    src/
      index.ts
    lib/
      index.js
    package.json
  shared/
    src/
      shared.ts
    lib/
      shared.js
    package.json

При развертывании службы Firebase вы можете прикрепитькоманды для ловушек до и после развертывания . Это делается в firebase.json через свойства predeploy и postdeploy требуемого сервиса. Эти свойства содержат массив последовательных команд, запускаемых до и после развертывания кода соответственно. Кроме того, эти команды вызываются с переменными среды RESOURCE_DIR (путь к каталогу ./functions или ./ionic-app, в зависимости от того, что применимо) и PROJECT_DIR (путь к каталогу, содержащий firebase.json).

Используя массив predeploy для functions внутри firebase.json, мы можем скопировать код общей библиотеки в папку, которая развернута в экземпляре Cloud Functions. Делая это, вы можете просто включить общий код, как если бы это была библиотека, расположенная в подпапке, или вы можете сопоставить его имя, используя отображение пути Typescript в tsconfig.json, с именованным модулем (так что вы можете использоватьimport { hiThere } from 'shared';).

Определение хука predeploy (для глобальной совместимости с Windows используется глобальная установка shx):

// firebase.json
{
  "functions": {
    "predeploy": [
      "shx rm -rf \"$RESOURCE_DIR/src/shared\"", // delete existing files
      "shx cp -R \"$PROJECT_DIR/shared/.\" \"$RESOURCE_DIR/src/shared\"", // copy latest version
      "npm --prefix \"$RESOURCE_DIR\" run lint", // lint & compile
      "npm --prefix \"$RESOURCE_DIR\" run build"
    ]
  },
  "hosting": {
    "public": "ionic-app",
    ...
  }
}

Связывание машинописной копии скопированной библиотекиИсходный код для конфигурации компилятора машинописи функций:

// functions/tsconfig.json
{
  "compilerOptions": {
    ...,
    "baseUrl": "./src",
    "paths": {
      "shared": ["shared/src"]
    }
  },
  "include": [
    "src"
  ],
  ...
}

Связывание имени модуля "shared" с папкой пакета скопированной библиотеки.

// functions/package.json
{
  "name": "functions",
  "scripts": {
    ...
  },
  "engines": {
    "node": "8"
  },
  "main": "lib/index.js",
  "dependencies": {
    "firebase-admin": "^8.6.0",
    "firebase-functions": "^3.3.0",
    "shared": "file:./src/shared",
    ...
  },
  "devDependencies": {
    "tslint": "^5.12.0",
    "typescript": "^3.2.2",
    "firebase-functions-test": "^0.1.6"
  },
  "private": true
}

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


Надеюсь, это вдохновит того, кто более знаком с компиляцией Typescript, найти более чистое решение, использующее эти хуки.
1 голос
/ 13 ноября 2019

Другое возможное решение, если вы используете git для управления своим кодом, это использование git submodule. Используя git submodule, вы можете включить в свой проект другой git-репозиторий.

Применяется к вашему варианту использования:

  1. Нажмите текущую версию вашего shared-git-repository
  2. Используйте git submodule add <shared-git-repository-link> внутри вашего основного проекта (-ов), чтобы связать общий репозиторий.

Вот ссылка на документацию: https://git -scm.com / docs/ ГИТ-подмодуль

1 голос
/ 14 ноября 2019

Возможно, вы захотите попробовать Lerna , инструмент для управления проектами JavaScript (и TypeScript) с несколькими пакетами.

Настройка

Предполагая, что ваш проект имеет следующееструктура каталогов:

packages
  ionic-app
    package.json
  firebase-functions
    package.json
  shared
    package.json

Убедитесь, что вы указали правильный уровень доступа (private и config/access ключи) во всех модулях, которые вы не хотите публиковать, а также в typingsзапись в вашем shared модуле:

Shared:

{
  "name": "shared",
  "version": "1.0.0",
  "private": true,
  "config": {
    "access": "private"
  },
  "main": "lib/index.js",
  "typings": "lib/index.d.ts",
  "scripts": {
    "compile": "tsc --project tsconfig.json"
  }
}

Ionic-app:

{
  "name": "ionic-app",
  "version": "1.0.0",
  "private": true,
  "config": {
    "access": "private"
  },
  "main": "lib/index.js",
  "scripts": {
    "compile": "tsc --project tsconfig.json"
  },
  "dependencies": {
    "shared": "1.0.0"
  }
}

С учетом вышеуказанных изменений вы можете создать root-level package.json, где вы можете указать любой devDependencies, к которому вы хотите, чтобы все ваши модули проекта имели доступ, например, ваш модуль модульного тестирования, tslint и т. д.

packages
  ionic-app
    package.json
  firebase-functions
    package.json
  shared
    package.json
package.json         // root-level, same as the `packages` dir

Вы также можете использовать этокорневой уровень package.json для определения сценариев npm, которые будут вызывать соответствующие сценарии в модулях вашего проекта (через lerna):

{
  "name": "my-project",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "compile": "lerna run compile --stream",
    "postinstall": "lerna bootstrap",
  },
  "devDependencies": {
    "lerna": "^3.18.4",
    "tslint": "^5.20.1",
    "typescript": "^3.7.2"
  },
}

После этого добавьте файл конфигурации lerna в корневой каталог:

packages
  ionic-app
    package.json
  firebase-functions
    package.json
  shared
    package.json
package.json
lerna.json

со следующим содержанием:

{
  "lerna": "3.18.4",
  "loglevel": "info",
  "packages": [
    "packages/*"
  ],
  "version": "1.0.0"
}

Теперь, когда вы запускаете npm install в корневом каталоге, сценарий postinstall, определенный на вашем корневом уровне package.json, вызовет lerna bootstrap.

Что lerna bootstrap делает так:он будет символически связывать ваш shared модуль с ionic-app/node_modules/shared и firebase-functions/node_modules/shared, поэтому с точки зрения этих двух модулей shared выглядит так же, как и любой другой модуль npm.

Компиляция

Конечно, символьных ссылок недостаточно, поскольку вам все еще нужно скомпилировать их из TypeScript в JavaScript.

Вот где вступает в действие сценарий корневого уровня package.json compile.

Когда вы запускаете npm run compile в корне вашего проекта, npm вызывает lerna run compile --stream, а lerna run compile --stream вызывает скрипт с именем compile в файле package.json каждого из ваших модулей.

Поскольку каждый из вашихУ модулей теперь есть свой собственный скрипт compile, у вас должен быть файл tsonfig.json на модуль. Если вам не нравится дублирование, вы можете использовать tsconfig корневого уровня или комбинацию файлов tsconfig корневого уровня и tsconfig уровня модуля, унаследованных от корневого.

Если выХотелось бы посмотреть, как эта установка работает в реальном проекте, взгляните на Serenity / JS , где я довольно широко ее использую.

Развертывание

Хорошая вещь о том, что модуль shared имеет символическую ссылку под node_modules под firebase-functions и ionic-app, а ваш devDepedencies под node_modules под корневым каталогом проекта, состоит в том, что если вам нужно развернуть потребительский модуль где-нибудь (так чтоionic-app, например), вы можете просто сжать все вместе с node_modules и не беспокоиться о необходимости удалять зависимости dev перед развертыванием.

Надеюсь, это поможет!

Jan

0 голосов
/ 15 ноября 2019

Если я правильно понимаю вашу проблему, решение является более сложным, чем один ответ, и оно частично зависит от ваших предпочтений.

Подход 1: Локальные копии

Вы можете использовать Gulp для автоматизации уже описанного вами рабочего решения, но IMO не очень прост в обслуживании и значительно увеличивает сложность, если в какой-то момент приходит другой разработчик.

Подход 2: Monorepo

Вы можете создать один репозиторий, содержащий все три папки, и соединить их так, чтобы они вели себя как один проект. Как уже было сказано выше, вы можете использовать Lerna . Это требует некоторой настройки, но после этого эти папки будут работать как один проект.

Подход 3: Компоненты

Рассматривать каждую из этих папок какавтономный компонент. Взгляните на Бит . Это позволит вам настроить папки как меньшие части большого проекта, и вы можете создать личную учетную запись, которая будет охватывать эти компоненты только для вас. После первоначальной настройки это позволит вам даже применять обновления к отдельным папкам, и родительская папка, которая их использует, автоматически получит обновления.

Подход 4: Пакеты

Вы специально сказали, что не хотите использовать npm, но я хочу поделиться им, потому что в настоящее время я работаю с настройкой, как описано ниже, и отлично справляюсь со мной:

  1. Используйте npm или yarn для создания пакета для каждой папки (вы можете создавать пакеты с областью действия для них обоих, так что код будет доступен только вам, если вас это интересует).
  2. Inродительская папка (которая использует все эти папки), созданные пакеты связаны как зависимости.
  3. Я использую webpack для связывания всего кода, используя псевдонимы пути webpack в сочетании с путями машинописи.

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

Примечание

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

Надеюсь, это поможет

***** ОБНОВЛЕНИЕ **** Для продолжения по пункту 4: я прошу прощения, мой плохой. Может быть, я ошибся, потому что, насколько я знаю, вы не можете символическую ссылку на модуль, если он не загружен. Тем не менее, вот оно:

  1. У вас есть отдельный модуль npm, давайте для этого воспользуемся firebase-functions. Вы компилируете его или используете raw ts, в зависимости от ваших предпочтений.
  2. В родительском проекте добавьте firebase-functions в качестве зависимости.
  3. В tsconfig.json, добавьте "paths": {"firebase-functions: ['node_modules/firebase-functions']"}
  4. В веб-пакете - resolve: {extensions: ['ts', 'js'], alias: 'firebase-functions': }

Таким образом, вы ссылаетесь на все свои экспортируемые функции из модуля firebase-functions просто используя import { Something } from 'firebase-functions'. Webpack и TypeScript свяжут его с папкой модулей узла. При такой конфигурации родительский проект не будет заботиться о том, написан ли модуль firebase-functions на языке TypeScript или ванильном javascript.

После настройки он отлично подойдет для производства. Затем для связи и работы в автономном режиме:

  1. Перейдите к firebase-functions project и напишите npm link. Он создаст символическую ссылку, локальную для вашего компьютера, и отобразит ссылку на имя, заданное вами в package.json.
  2. Перейдите к родительскому проекту и напишите npm link firebase-functions, что создаст символическую ссылку и отобразит зависимостьfirebase-функции для папки, в которой вы ее создали.
0 голосов
/ 13 ноября 2019

Инструмент, который вы ищете - npm link. npm link предоставляет символические ссылки на локальный пакет npm. Таким образом, вы можете связать пакет и использовать его в своем основном проекте, не публикуя его в библиотеке пакетов npm.

Применимо к вашему варианту использования:

  1. Использование npm link внутри вашегоshared пакет. Это установит назначение символической ссылки для будущих установок.
  2. Перейдите к вашему основному проекту (ам). Внутри вашего functions пакета и используйте npm link shared, чтобы связать общий пакет и добавить его в каталог node_modules.

Вот ссылка на документацию: https://docs.npmjs.com/cli/link.html

...