Utilizzo di un modulo nodo condiviso per le classi comuni


15

Obbiettivo

Quindi sto avendo un progetto con questa struttura:

  • ionici app
  • Firebase funzioni
  • condivisa

L'obiettivo è definire interfacce e classi comuni nel sharedmodulo.

restrizioni

Non voglio caricare il mio codice su npm per usarlo localmente e non ho intenzione di caricarlo affatto. Dovrebbe funzionare al 100% offline.

Mentre il processo di sviluppo dovrebbe funzionare offline, i moduli ionic-appe firebase-functionsverranno distribuiti su Firebase (hosting e funzioni). Pertanto, il codice dalshared modulo dovrebbe essere disponibile lì.

Quello che ho provato finora

  • Ho provato a utilizzare i riferimenti di progetto in dattiloscritto, ma non mi sono avvicinato al lavoro
  • L'ho provato installandolo come modulo npm come nella seconda risposta a questa domanda
    • All'inizio sembra funzionare bene, ma durante la compilazione, quando eseguo ho un errore del genere 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)

Domanda

Hai una soluzione per creare un modulo condiviso usando sia typescripts config, sia NPM?

Non contrassegnarlo come duplicato → Ho provato qualsiasi soluzione trovata su StackOverflow.

Informazioni addizionali

Config per condiviso:

// 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"
  }
}

Configurazione per funzioni:

// 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"
  }
}

Soution attuale

Ho aggiunto uno script npm al modulo condiviso, che copia tutti i file (senza index.js) negli altri moduli. Questo ha il problema, che controllo il codice duplicato in SCM e che devo eseguire quel comando ad ogni modifica. Inoltre, l'IDE lo considera solo come file diverso.

Risposte:


4

Prefazione: non conosco molto bene come funziona la compilazione di Typescript e comepackage.json dovrebbe essere definito in tale modulo. Questa soluzione, sebbene funzioni, potrebbe essere considerata un modo confuso per raggiungere il compito da svolgere.

Supponendo la seguente struttura di directory:

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

Quando si distribuisce un servizio Firebase, è possibile associare comandi agli hook di pre-distribuzione e post-distribuzione . Questo viene fatto firebase.jsontramite le proprietà predeploye postdeploysul servizio desiderato. Queste proprietà contengono una serie di comandi sequenziali da eseguire prima e dopo la distribuzione del codice rispettivamente. Inoltre, questi comandi vengono chiamati con le variabili di ambiente RESOURCE_DIR(il percorso della directory di ./functionso./ionic-app , a seconda dei casi applicabile) e PROJECT_DIR(il percorso della directory contenente firebase.json).

Utilizzando l' predeployarray per functionsinterni firebase.json, è possibile copiare il codice della libreria condivisa nella cartella distribuita nell'istanza di Funzioni cloud. In questo modo, puoi semplicemente includere il codice condiviso come se fosse una libreria situata in una sottocartella oppure puoi mappare il suo nome usando il mapping dei percorsi di Typescript intsconfig.json un modulo con nome (così puoi usareimport { hiThere } from 'shared'; ).

La predeploydefinizione hook (utilizza l'installazione globale di shxper la compatibilità di Windows):

// 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",
    ...
  }
}

Collegamento della fonte dattiloscritta della libreria copiata alla configurazione del compilatore dattiloscritto delle funzioni:

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

Associando il nome del modulo, "condiviso", alla cartella del pacchetto della libreria copiata.

// 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
}

Lo stesso approccio può essere utilizzato con la cartella di hosting.


Spero che questo ispiri qualcuno che abbia più familiarità con la compilazione di Typescript a trovare una soluzione più pulita che faccia uso di questi ganci.


3

Potresti provare Lerna , uno strumento per la gestione di progetti JavaScript (e TypeScript) con più pacchetti.

Impostare

Supponendo che il progetto abbia la seguente struttura di directory:

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

Assicurati di specificare il livello di accesso ( privatee le config/accesschiavi) corretto in tutti i moduli che non desideri pubblicare, così come la typingsvoce nel tuo sharedmodulo:

Condivisa:

{
  "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"
  }
}

Ionico-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"
  }
}

Con le modifiche sopra riportate, è possibile creare un livello radice in package.jsoncui è possibile specificare devDependenciesquello a cui si desidera avere accesso a tutti i moduli del progetto, come il framework di test delle unità, tslint, ecc.

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

Puoi anche usare questo livello package.jsondi root per definire gli script npm che richiameranno gli script corrispondenti nei moduli del tuo progetto (via 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"
  },
}

Con quello in atto, aggiungi il file di configurazione di lerna nella tua directory principale:

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

con i seguenti contenuti:

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

Ora quando si esegue npm installnella directory principale, verrà richiamato lo postinstallscript definito nel proprio livello principalepackage.jsonlerna bootstrap .

Ciò che lerna bootstrapfa è che collegherà in modo simbolico il tuo sharedmodulo a , ionic-app/node_modules/sharede firebase-functions/node_modules/sharedquindi dal punto di quei due moduli,shared sembra proprio come qualsiasi altro modulo npm.

Compilazione

Naturalmente, il collegamento simbolico dei moduli non è sufficiente in quanto è ancora necessario compilarli da TypeScript a JavaScript.

È qui che package.json compileentra in gioco lo script a livello di root .

Quando si esegue npm run compilenella radice del progetto, npm invocherà lerna run compile --streame invocherà lerna run compile --streamlo script chiamato compilein ciascuno dei package.jsonfile dei moduli .

Dal momento che ognuno dei tuoi moduli ora ha il suo compilescript, dovresti avere untsonfig.json file per modulo. Se non ti piace la duplicazione, potresti cavartela con un tsconfig a livello di root o una combinazione di un file tsconfig a livello di root e di un file tsconfig a livello di modulo che eredita da quello di root.

Se desideri vedere come funziona questa configurazione su un progetto del mondo reale, dai un'occhiata a Serenity / JS dove l'ho usato abbastanza ampiamente.

Distribuzione

La cosa bella di avere il sharedmodulo di link simbolico sotto node_modulessotto firebase-functionse ionic-app, e il vostro devDepedenciessotto node_modulessotto radice del progetto è che se è necessario distribuire l'ovunque modulo di consumatori (in modo che il ionic-appper esempio), si può solo comprimere il tutto insieme al suo node_modulese non preoccuparsi dover rimuovere le dipendenze degli sviluppatori prima della distribuzione.

Spero che sia di aiuto!

Jan


Intresting! Lo verificherò definitivamente e cercherò se questa è la soluzione giusta.
MauriceNino,

2

Un'altra possibile soluzione, se stai usando git per gestire il tuo codice, sta usando git submodule. Usando git submodulesei in grado di includere un altro repository git nel tuo progetto.

Applicato al tuo caso d'uso:

  1. Esegui il push della versione corrente del tuo repository git condiviso
  2. Utilizzare git submodule add <shared-git-repository-link>all'interno dei progetti principali per collegare il repository condiviso.

Ecco un link alla documentazione: https://git-scm.com/docs/git-submodule


In realtà non è una cattiva idea, ma lo sviluppo e i test locali sono sostanzialmente andati con questo approccio.
MauriceNino,

0

Se capisco correttamente il tuo problema, la soluzione è più complessa di una singola risposta e dipende in parte dalle tue preferenze.

Approccio 1: copie locali

Puoi usare Gulp per automatizzare la soluzione di lavoro che hai già descritto, ma IMO non è molto facile da mantenere e aumenta drasticamente la complessità se a un certo punto arriva un altro sviluppatore.

Approccio 2: Monorepo

È possibile creare un singolo repository che contiene tutte e tre le cartelle e collegarle in modo che si comportino come un singolo progetto. Come già detto sopra, puoi usare Lerna . Richiede un po 'di una configurazione, ma una volta fatto, quelle cartelle si comporteranno come un singolo progetto.

Approccio 3: componenti

Tratta ciascuna di queste cartelle come un componente autonomo. Dai un'occhiata a Bit . Ti consentirà di impostare le cartelle come parti più piccole di un progetto più grande e puoi creare un account privato che includerà tali componenti solo per te. Una volta impostato inizialmente, ti permetterà anche di applicare gli aggiornamenti alle cartelle separate e quello principale che le utilizza riceverà automaticamente gli aggiornamenti.

Approccio 4: pacchetti

Hai detto espressamente che non vuoi usare npm, ma voglio condividerlo, perché attualmente sto lavorando con una configurazione come descritto di seguito e sto facendo un lavoro perfetto per me:

  1. Usa npmoyarn per creare un pacchetto per ogni cartella (è possibile creare pacchetti con ambito per entrambi, in modo che il codice sia disponibile solo per te, se questo è il tuo problema).
  2. Nella cartella principale (che utilizza tutte queste cartelle), i pacchetti creati sono collegati come dipendenze.
  3. Uso il webpack per raggruppare tutto il codice, usando gli alias del percorso del webpack in combinazione con i percorsi dattiloscritti.

Funziona come un incantesimo e quando i pacchetti sono collegati in modo simbolico per lo sviluppo locale funziona completamente offline e secondo la mia esperienza - ogni cartella è scalabile separatamente e molto facile da mantenere.

Nota

I pacchetti 'figlio' sono già precompilati nel mio caso in quanto sono piuttosto grandi e ho creato tsconfigs separati per ogni pacchetto, ma la cosa bella è che puoi cambiarlo facilmente. In passato ho usato sia dattiloscritto nel modulo e file compilati, sia file js grezzi, quindi l'intera cosa è molto, molto versatile.

Spero che sia di aiuto

***** AGGIORNAMENTO **** Per continuare al punto 4: mi scuso, mio ​​male. Forse ho sbagliato perché, per quanto ne so, non è possibile collegare simbolicamente un modulo se non è caricato. Tuttavia, eccolo qui:

  1. Hai un modulo npm separato, usiamolo firebase-functionsper quello. Lo compili o usi i ts grezzi, a seconda delle tue preferenze.
  2. Nel progetto principale aggiungere firebase-functionscome dipendenza.
  3. In tsconfig.json, aggiungi"paths": {"firebase-functions: ['node_modules/firebase-functions']"}
  4. Nel webpack - resolve: {extensions: ['ts', 'js'], alias: 'firebase-functions': }

In questo modo, fai riferimento a tutte le tue funzioni esportate dal firebase-functionsmodulo semplicemente usando import { Something } from 'firebase-functions'. Webpack e TypeScript lo collegheranno alla cartella dei moduli del nodo. Con questa configurazione, al progetto genitore non importa se il firebase-functionsmodulo è scritto in TypeScript o JavaScript vaniglia.

Una volta installato, funzionerà perfettamente per la produzione. Quindi, per collegarti e lavorare offline:

  1. Passare al firebase-functionsprogetto e scrivere npm link. Creerà un collegamento simbolico, locale al tuo computer e mapperà il collegamento con il nome impostato in package.json.
  2. Passare al progetto principale e scrivere npm link firebase-functions, che creerà il collegamento simbolico e mapperà la dipendenza delle funzioni firebase nella cartella in cui è stato creato.

Penso che tu abbia frainteso qualcosa. Non ho mai detto di non voler usare npm. In effetti tutti e tre questi moduli sono moduli nodo. Ho appena detto che non voglio caricare i miei moduli su npm. Puoi approfondire un po 'di più la quarta parte - sembra interessante? forse fornire un esempio di codice?
MauriceNino,

Aggiungerò un'altra risposta, poiché sarà lungo e illeggibile come commento
Ivan Dzhurov

Aggiornato la mia risposta iniziale, spero sia più chiaro
Ivan Dzhurov,

0

Non voglio caricare il mio codice su npm per usarlo localmente e non ho intenzione di caricarlo affatto. Dovrebbe funzionare al 100% offline.

Tutti i moduli npm sono installati localmente e funzionano sempre offline, ma se non si desidera pubblicare i pacchetti in modo che le persone possano vederlo, è possibile installare il registro npm privato.

ProGet è un server di repository privato NuGet / Npm disponibile per Windows che è possibile utilizzare nel proprio ambiente di sviluppo / produzione privato per ospitare, accedere e pubblicare i propri pacchetti privati. Anche se è su Windows, ma sono sicuro che ci sono varie alternative disponibili su Linux.

  1. Git Submodules è una cattiva idea, è davvero un vecchio modo di condividere il codice che non ha la versione come i pacchetti, cambiare e commettere i sottomoduli è una vera seccatura.
  2. Anche la cartella di importazione di origine è una cattiva idea, anche in questo caso il problema è il controllo delle versioni, perché se qualcuno modifica la cartella dipendente nel repository dipendente, il monitoraggio è di nuovo un incubo.
  3. Qualsiasi strumento di terze parti con script per emulare la separazione dei pacchetti è una perdita di tempo poiché npm fornisce già una gamma di strumenti per gestire i pacchetti così bene.

Ecco il nostro scenario di compilazione / distribuzione.

  1. Ogni pacchetto privato ha .npmrcquale contiene registry=https://private-npm-repository.
  2. Pubblichiamo tutti i nostri pacchetti privati ​​sul nostro repository ProGet ospitato privatamente.
  3. Ogni pacchetto privato contiene pacchetti privati ​​dipendenti su ProGet.
  4. Il nostro server di build accede a ProGet tramite l'autenticazione npm impostata da noi. Nessuno al di fuori della nostra rete ha accesso a questo repository.
  5. Il nostro server di creazione crea un pacchetto npm con il bundled dependenciesquale contiene tutti i pacchetti all'interno node_modulese il server di produzione non deve mai accedere ai pacchetti NPM o NPM privati ​​poiché tutti i pacchetti necessari sono già raggruppati.

L'uso del repository npm privato presenta vari vantaggi,

  1. Non è necessario uno script personalizzato
  2. Si adatta alla pipeline di buid / publishing del nodo
  3. Ogni pacchetto npm privato conterrà un collegamento diretto al controllo del codice sorgente git privato, facile da eseguire il debug e investigare errori in futuro
  4. Ogni pacchetto è un'istantanea di sola lettura, quindi una volta pubblicato non può essere modificato e mentre si stanno creando nuove funzionalità, la base di codice esistente con la versione precedente di pacchetti dipendenti non sarà interessata.
  5. Puoi facilmente rendere pubblici alcuni pacchetti e spostarti in altri repository in futuro
  6. Se il software del tuo provider npm privato cambia, ad esempio decidi di spostare il codice nel cloud di registro del pacchetto npm privato del nodo, non dovrai apportare modifiche al codice.

Questa potrebbe essere una soluzione, ma sfortunatamente non fa per me. Grazie per il tuo tempo però!
MauriceNino,

Esiste anche un repository npm locale che viene installato come un piccolo nodo server, verdaccio.org
Akash Kava,

-1

Lo strumento che stai cercando è npm link. npm linkfornisce collegamenti simbolici a un pacchetto npm locale. In questo modo è possibile collegare un pacchetto e utilizzarlo nel progetto principale senza pubblicarlo nella libreria dei pacchetti npm.

Applicato al tuo caso d'uso:

  1. Utilizzare npm linkall'interno del sharedpacchetto. Ciò imposterà la destinazione del collegamento simbolico per installazioni future.
  2. Passa ai tuoi progetti principali. All'interno del functionspacchetto e utilizzare npm link sharedper collegare il pacchetto condiviso e aggiungerlo alla node_modulesdirectory.

Ecco un link alla documentazione: https://docs.npmjs.com/cli/link.html


Per quanto ne so, il collegamento npm è solo a scopo di test e non funziona se si desidera distribuire il codice risultante (ad esempio le mie funzioni).
MauriceNino,

Vedo, probabilmente dovresti aggiungere questo requisito alla tua domanda.
Friedow,

È già menzionato nella domanda, ma lo chiarirò.
MauriceNino,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.