import * as fs from 'fs-extra' ; import * as _ from 'lodash' ; import * as path from 'path' ; import * as gulp from 'gulp' ; import * as gulpBabel from 'gulp-babel' ; import { globalState } from './global-state' ; import { logFatal , spinner } from './log' ; import { IAfterProdBuild , IAnalyseProject , IBuildConfigPipe , ICreateEntry , IEnsureProjectFilesQueue , ILintFilter , ILoaderExcludePipe , ILoaderIncludePipe , ILoaderOptionsPipe , IPluginModule , IWhiteFile , CommandRegister , ProjectType , IDevDllList , IJestConfigPipe , } from './define' ; import { getBabelOptions } from './babel-options' ; import { tempPath , srcPath } from '../node' ; import * as pluginClientSsr from '../built-in-plugins/client-ssr' ; import * as pluginCommandAnalyse from '../built-in-plugins/command-analyse' ; import * as pluginCommandBuild from '../built-in-plugins/command-build' ; import * as pluginCommandBundle from '../built-in-plugins/command-bundle' ; import * as pluginCommandDev from '../built-in-plugins/command-dev' ; import * as pluginCommandDocs from '../built-in-plugins/command-docs' ; import * as pluginCommandInit from '../built-in-plugins/command-init' ; import * as pluginCommandPreview from '../built-in-plugins/command-preview' ; import * as pluginCommandPublish from '../built-in-plugins/command-publish' ; import * as pluginCommandLint from '../built-in-plugins/command-lint' ; import * as pluginCommandTest from '../built-in-plugins/command-test' ; import * as pluginEnsureProjectFiles from '../built-in-plugins/ensure-project-files' ; import * as pluginMocks from '../built-in-plugins/mocks' ; import * as pluginProjectAnalyseConfig from '../built-in-plugins/project-analyse-config' ; import * as pluginProjectAnalyseLayouts from '../built-in-plugins/project-analyse-layouts' ; import * as pluginProjectAnalyseNotFound from '../built-in-plugins/project-analyse-not-found' ; import * as pluginProjectAnalysePages from '../built-in-plugins/project-analyse-pages' ; import * as pluginServiceWorker from '../built-in-plugins/service-worker' ; import * as whiteFiles from '../built-in-plugins/white-files' ; export const loadedPlugins = new Set < IPluginModule > ( ) ; export class IPluginConfig { public analyseInfo ? : any = { } ; public commands ? : CommandRegister [ ] = [ ] ; public buildConfigPipes : IBuildConfigPipe [ ] = [ ] ; public bundleConfigPipes : IBuildConfigPipe [ ] = [ ] ; public buildConfigStyleLoaderOptionsPipes : ILoaderOptionsPipe [ ] = [ ] ; public buildConfigCssLoaderOptionsPipes : ILoaderOptionsPipe [ ] = [ ] ; public buildConfigSassLoaderOptionsPipes : ILoaderOptionsPipe [ ] = [ ] ; public buildConfigLessLoaderOptionsPipes : ILoaderOptionsPipe [ ] = [ ] ; public buildConfigBabelLoaderOptionsPipes : ILoaderOptionsPipe [ ] = [ ] ; public buildConfigJsLoaderIncludePipes : ILoaderIncludePipe [ ] = [ ] ; public buildConfigSassLoaderIncludePipes : ILoaderIncludePipe [ ] = [ ] ; public buildConfigLessLoaderIncludePipes : ILoaderIncludePipe [ ] = [ ] ; public buildConfigJsLoaderExcludePipes : ILoaderExcludePipe [ ] = [ ] ; public buildConfigSassLoaderExcludePipes : ILoaderExcludePipe [ ] = [ ] ; public buildConfigLessLoaderExcludePipes : ILoaderExcludePipe [ ] = [ ] ; public buildAfterProdBuild : IAfterProdBuild [ ] = [ ] ; public projectAnalyses : IAnalyseProject [ ] = [ ] ; public projectCreateEntrys : ICreateEntry [ ] = [ ] ; public whiteFileRules : IWhiteFile [ ] = [ ] ; public ensureProjectFilesQueue : IEnsureProjectFilesQueue [ ] = [ ] ; public devServices : { socketListeners : { name : string ; callback : ( ) => void ; } [ ] ; } = { socketListeners : [ ] } ; public lintFilters : ILintFilter [ ] = [ ] ; public devDllPipes : IDevDllList [ ] = [ ] ; public jestConfigPipes : IJestConfigPipe [ ] = [ ] ; public initType : ProjectType = null ; } export const plugin = new IPluginConfig ( ) ; let hasInitPlugins = false ; const buildLocalPlugin = ( packagePath , outdir ) => { const targetPath = path . join ( packagePath , srcPath . dir , '**/*.{ts,tsx}' ) ; return new Promise ( ( resolve , reject ) => { gulp . src ( targetPath ) . pipe ( gulpBabel ( getBabelOptions ( ) ) ) . on ( 'error' , reject ) . pipe ( gulp . dest ( outdir ) ) . on ( 'end' , resolve ) ; } ) ; } ; export const loadPlugins = async ( pluginIncludeRoots = [ ] ) => { if ( hasInitPlugins ) { return ; } hasInitPlugins = true ; const builtInPlugins = [ pluginClientSsr , pluginCommandAnalyse , pluginCommandBuild , pluginCommandBundle , pluginCommandDev , pluginCommandDocs , pluginCommandInit , pluginCommandPreview , pluginCommandPublish , pluginCommandLint , pluginCommandTest , pluginEnsureProjectFiles , pluginMocks , whiteFiles , pluginProjectAnalyseConfig , pluginProjectAnalyseLayouts , pluginProjectAnalyseNotFound , pluginProjectAnalysePages , pluginServiceWorker , ] ; builtInPlugins . forEach ( eachPlugin => { loadedPlugins . add ( eachPlugin ) ; } ) ; if ( globalState . sourceConfig . type !== 'plugin' ) { await getPriPlugins ( globalState . projectRootPath , pluginIncludeRoots . concat ( globalState . projectRootPath ) . map ( pluginIncludeRoot => { return path . join ( pluginIncludeRoot , 'package.json' ) ; } ) ) ; } if ( loadedPlugins . size > 1 ) { for ( const eachPlugin of getPluginsByOrder ( ) ) { const pluginInstance = await eachPlugin . getPlugin ( ) ; if ( pluginInstance . main ) { await pluginInstance . main ( ) ; } } } } ; async function getPriPlugins ( pluginRootPath , packageJsonPaths ) { if ( globalState . sourceConfig . type === 'plugin' ) { return ; } if ( pluginRootPath === globalState . projectRootPath ) { for ( const eachPackage of globalState . plugins ) { const distPath = path . join ( globalState . projectRootPath , tempPath . dir , 'plugins' , eachPackage . name ) ; await spinner ( `Build plugin ${ eachPackage . name }` , async ( ) => { await fs . remove ( distPath ) ; await buildLocalPlugin ( eachPackage . rootPath , distPath ) ; } ) ; addPluginFromEntry ( distPath ) ; } } const deps = packageJsonPaths . map ( packageJsonPath => { return getDependencesByPackageJsonPath ( packageJsonPath ) ; } ) ; const allDependencies = deps . reduce ( ( obj , next ) => { Object . assign ( obj , next ) ; return obj ; } , { } ) ; Object . keys ( allDependencies ) . filter ( subPackageName => { return subPackageName . startsWith ( 'pri-plugin' ) || subPackageName . startsWith ( '@ali/pri-plugin' ) ; } ) . map ( subPackageName => { if ( Array . from ( loadedPlugins ) . some ( loadedPlugin => { return loadedPlugin . getConfig ( ) . name === subPackageName ; } ) ) { logFatal ( `There are two plugins named ${ subPackageName }!` ) ; } const subPackageVersion = allDependencies [ subPackageName ] ; const subPackageRealEntry = subPackageVersion . startsWith ( 'file:' ) ? path . join ( pluginRootPath , subPackageVersion . replace ( /^file:/g , '' ) ) : subPackageName ; const subPackageRealEntryFilePath = require . resolve ( subPackageRealEntry , { paths : [ __dirname , pluginRootPath ] , } ) ; const subPackageAbsolutePath = ! subPackageVersion . startsWith ( 'file:' ) ? getPackageJsonPathByPathOrNpmName ( subPackageName , pluginRootPath ) : path . resolve ( pluginRootPath , subPackageVersion . replace ( /^file:/g , '' ) , 'package.json' ) ; addPluginFromEntry ( subPackageRealEntryFilePath ) ; if ( subPackageAbsolutePath ) { getPriPlugins ( path . resolve ( subPackageAbsolutePath , '..' ) , [ subPackageAbsolutePath ] ) ; } } ) ; } export function getPluginsByOrder ( ) { const instantiatedPluginNames = new Set < string > ( ) ; const outputPlugins = [ ] ; loadedPlugins . forEach ( loadedPlugin => { if ( loadedPlugin . getConfig ( ) . dependencies ) { loadedPlugin . getConfig ( ) . dependencies . forEach ( depPluginName => { if ( ! Array . from ( loadedPlugins ) . some ( eachLoadedPlugin => { return eachLoadedPlugin . getConfig ( ) . name === depPluginName ; } ) ) { logFatal ( `${ loadedPlugin . getConfig ( ) . name }: No dependent "${ depPluginName }"\nTry: npm install ${ depPluginName }.` ) ; } } ) ; } } ) ; while ( instantiatedPluginNames . size !== loadedPlugins . size ) { const currentInstantiatedPlugins = getPluginWithPreloadDependences ( Array . from ( instantiatedPluginNames ) ) ; currentInstantiatedPlugins . forEach ( eachPlugin => { return outputPlugins . push ( eachPlugin ) ; } ) ; if ( currentInstantiatedPlugins . length === 0 ) { throw Error ( 'Plugin loop dependency.' ) ; } currentInstantiatedPlugins . forEach ( eachPlugin => { return instantiatedPluginNames . add ( eachPlugin . getConfig ( ) . name ) ; } ) ; } return outputPlugins ; } function getPluginWithPreloadDependences ( preInstantiatedDependences ) { return ( Array . from ( loadedPlugins ) . filter ( loadedPlugin => { if ( preInstantiatedDependences . length === 0 ) { return true ; } return ( preInstantiatedDependences . findIndex ( pluginName => { return loadedPlugin . getConfig ( ) . name === pluginName ; } ) === - 1 ) ; } ) . filter ( loadedPlugin => { if ( ! loadedPlugin . getConfig ( ) . dependencies ) { return true ; } if ( loadedPlugin . getConfig ( ) . dependencies . every ( depPluginName => { return preInstantiatedDependences . indexOf ( depPluginName ) > - 1 ; } ) ) { return true ; } return false ; } ) . map ( loadedPlugin => { return loadedPlugin ; } ) ) ; } function getPackageJsonPathByPathOrNpmName ( pathOrNpmName , currentRootPath ) { try { return require . resolve ( path . join ( pathOrNpmName , 'package.json' ) , { paths : [ __dirname , currentRootPath ] } ) ; } catch ( error ) { return null ; } } function getDependencesByPackageJsonPath ( packageJsonPath ) { const packageJsonExist = fs . existsSync ( packageJsonPath ) ; const packageJson = packageJsonExist ? fs . readJsonSync ( packageJsonPath ) : null ; return { ... _ . get ( packageJson , 'dependencies' , { } ) , ... _ . get ( packageJson , 'devDependencies' , { } ) , } ; } function addPluginFromEntry ( entryPath ) { const instance = require ( entryPath ) ; if ( ! instance . getConfig ) { logFatal ( 'Plugin must impletement getConfig method!' ) ; } if ( ! instance . getPlugin ) { logFatal ( 'Plugin must impletement getPlugin method!' ) ; } if ( ! instance . getConfig ( ) . name ) { logFatal ( 'Plugin must have name!' ) ; } loadedPlugins . add ( instance ) ; }