single-spa / single-spa-angular

Helpers for building single-spa applications which use Angular
Apache License 2.0
199 stars 78 forks source link

Converting existing Angular 4 app to single-spa #494

Open arstrel opened 11 months ago

arstrel commented 11 months ago

Question

I need some help with migrating existing Angular 4 application to single-spa please. :pray: The docs explicitly mention that Angular 4 is supported, but I absolutely can't find any examples with Angular 4 so I would appreciate any help in the matter.

According to the documentation Angular v4 requires some manual installation steps:

  1. I've installed single-spa-angular@3
  2. I've created all the files, including the ones in subdirectories from this github (as .js or .ts and removed <% where applicable to the best of my ability. Although there are some places with template conditionals and it is not obvious what should be left and what should be deleted )
  3. I've added new scripts to package.json as described in this code
    "build:single-spa:sd": "ng build sd --prod",
    "serve:single-spa:sd": "ng s --project sd --disable-host-check --port 4200 --live-reload false",

Now the issue lies in the docs section about build process "Use Angular Builder"

My Angular 4 application uses angular-cli ""@angular/cli": "^1.7.4" . The docs instruct me to change angular.json file and set builder property there. However, Angular 4 application does not have angular.json file but uses angular-cli.json file instead. angular-cli.json file does not have anything at all about build process or architect property. -------------- I'm stuck here ------------- because I don't understand how to build angular 4 application as a html-less and serve it as .js bundle such that I can register it in react root app, created with create-spa-app cli tool


Since there is nothing about build config in angular-cli.json and it is all hidden in ng, I considered to do ng eject. That proved to be way more involved. ng eject outputs dev version of webpack.config.js. And for prod there is ng eject --prod. Both of these configs are huge (600 lines) and surely do not succeed currently.

The goal here is to modify the configuration so that it mimics what the single-spa-angular build/serve builders do for newer CLI versions. But I certainly have no idea of how to achieve it.

Webpack dev config. Created by `ng eject` ```js const fs = require('fs'); const path = require('path'); const CopyWebpackPlugin = require('copy-webpack-plugin'); const ProgressPlugin = require('webpack/lib/ProgressPlugin'); const CircularDependencyPlugin = require('circular-dependency-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const rxPaths = require('rxjs/_esm5/path-mapping'); const autoprefixer = require('autoprefixer'); const postcssUrl = require('postcss-url'); const postcssImports = require('postcss-import'); const { NoEmitOnErrorsPlugin, SourceMapDevToolPlugin, NamedModulesPlugin } = require('webpack'); const { ScriptsWebpackPlugin, NamedLazyChunksWebpackPlugin, BaseHrefWebpackPlugin, PostcssCliResources } = require('@angular/cli/plugins/webpack'); const { CommonsChunkPlugin } = require('webpack').optimize; const { AotPlugin } = require('@ngtools/webpack'); const nodeModules = path.join(process.cwd(), 'node_modules'); const realNodeModules = fs.realpathSync(nodeModules); const genDirNodeModules = path.join(process.cwd(), 'src', '$$_gendir', 'node_modules'); const entryPoints = ["inline","polyfills","sw-register","styles","scripts","vendor","main"]; const hashFormat = {"chunk":"","extract":"","file":".[hash:20]","script":""}; const baseHref = ""; const deployUrl = ""; const projectRoot = process.cwd(); const maximumInlineSize = 10; const postcssPlugins = function (loader) { return [ postcssImports({ resolve: (url, context) => { return new Promise((resolve, reject) => { let hadTilde = false; if (url && url.startsWith('~')) { url = url.substr(1); hadTilde = true; } loader.resolve(context, (hadTilde ? '' : './') + url, (err, result) => { if (err) { if (hadTilde) { reject(err); return; } loader.resolve(context, url, (err, result) => { if (err) { reject(err); } else { resolve(result); } }); } else { resolve(result); } }); }); }, load: (filename) => { return new Promise((resolve, reject) => { loader.fs.readFile(filename, (err, data) => { if (err) { reject(err); return; } const content = data.toString(); resolve(content); }); }); } }), postcssUrl({ filter: ({ url }) => url.startsWith('~'), url: ({ url }) => { const fullPath = path.join(projectRoot, 'node_modules', url.substr(1)); return path.relative(loader.context, fullPath).replace(/\\/g, '/'); } }), postcssUrl([ { // Only convert root relative URLs, which CSS-Loader won't process into require(). filter: ({ url }) => url.startsWith('/') && !url.startsWith('//'), url: ({ url }) => { if (deployUrl.match(/:\/\//) || deployUrl.startsWith('/')) { // If deployUrl is absolute or root relative, ignore baseHref & use deployUrl as is. return `${deployUrl.replace(/\/$/, '')}${url}`; } else if (baseHref.match(/:\/\//)) { // If baseHref contains a scheme, include it as is. return baseHref.replace(/\/$/, '') + `/${deployUrl}/${url}`.replace(/\/\/+/g, '/'); } else { // Join together base-href, deploy-url and the original URL. // Also dedupe multiple slashes into single ones. return `/${baseHref}/${deployUrl}/${url}`.replace(/\/\/+/g, '/'); } } }, { // TODO: inline .cur if not supporting IE (use browserslist to check) filter: (asset) => { return maximumInlineSize > 0 && !asset.hash && !asset.absolutePath.endsWith('.cur'); }, url: 'inline', // NOTE: maxSize is in KB maxSize: maximumInlineSize, fallback: 'rebase', }, { url: 'rebase' }, ]), PostcssCliResources({ deployUrl: loader.loaders[loader.loaderIndex].options.ident == 'extracted' ? '' : deployUrl, loader, filename: `[name]${hashFormat.file}.[ext]`, }), autoprefixer({ grid: true }), ]; }; module.exports = { "resolve": { "extensions": [ ".ts", ".js" ], "symlinks": true, "modules": [ "./src", "./node_modules" ], "alias": rxPaths(), "mainFields": [ "browser", "module", "main" ] }, "resolveLoader": { "modules": [ "./node_modules", "./node_modules/@angular/cli/node_modules" ], "alias": rxPaths() }, "entry": { "main": [ "./src/main.ts" ], "polyfills": [ "./src/polyfills.ts" ], "styles": [ "./src/styles.scss", "./node_modules/plottable/plottable.css", "./node_modules/snazzy-info-window/dist/snazzy-info-window.css" ] }, "output": { "path": path.join(process.cwd(), "dist"), "filename": "[name].bundle.js", "chunkFilename": "[id].chunk.js", "crossOriginLoading": false }, "module": { "rules": [ { "test": /\.html$/, "loader": "raw-loader" }, { "test": /\.(eot|svg|cur)$/, "loader": "file-loader", "options": { "name": "[name].[hash:20].[ext]", "limit": 10000 } }, { "test": /\.(jpg|png|webp|gif|otf|ttf|woff|woff2|ani)$/, "loader": "url-loader", "options": { "name": "[name].[hash:20].[ext]", "limit": 10000 } }, { "exclude": [ path.join(process.cwd(), "src/styles.scss"), path.join(process.cwd(), "node_modules/plottable/plottable.css"), path.join(process.cwd(), "node_modules/snazzy-info-window/dist/snazzy-info-window.css") ], "test": /\.css$/, "use": [ { "loader": "raw-loader" }, { "loader": "postcss-loader", "options": { "ident": "embedded", "plugins": postcssPlugins, "sourceMap": true } } ] }, { "exclude": [ path.join(process.cwd(), "src/styles.scss"), path.join(process.cwd(), "node_modules/plottable/plottable.css"), path.join(process.cwd(), "node_modules/snazzy-info-window/dist/snazzy-info-window.css") ], "test": /\.scss$|\.sass$/, "use": [ { "loader": "raw-loader" }, { "loader": "postcss-loader", "options": { "ident": "embedded", "plugins": postcssPlugins, "sourceMap": true } }, { "loader": "sass-loader", "options": { "sourceMap": true, "precision": 8, "includePaths": [] } } ] }, { "exclude": [ path.join(process.cwd(), "src/styles.scss"), path.join(process.cwd(), "node_modules/plottable/plottable.css"), path.join(process.cwd(), "node_modules/snazzy-info-window/dist/snazzy-info-window.css") ], "test": /\.less$/, "use": [ { "loader": "raw-loader" }, { "loader": "postcss-loader", "options": { "ident": "embedded", "plugins": postcssPlugins, "sourceMap": true } }, { "loader": "less-loader", "options": { "sourceMap": true } } ] }, { "exclude": [ path.join(process.cwd(), "src/styles.scss"), path.join(process.cwd(), "node_modules/plottable/plottable.css"), path.join(process.cwd(), "node_modules/snazzy-info-window/dist/snazzy-info-window.css") ], "test": /\.styl$/, "use": [ { "loader": "raw-loader" }, { "loader": "postcss-loader", "options": { "ident": "embedded", "plugins": postcssPlugins, "sourceMap": true } }, { "loader": "stylus-loader", "options": { "sourceMap": true, "paths": [] } } ] }, { "include": [ path.join(process.cwd(), "src/styles.scss"), path.join(process.cwd(), "node_modules/plottable/plottable.css"), path.join(process.cwd(), "node_modules/snazzy-info-window/dist/snazzy-info-window.css") ], "test": /\.css$/, "use": [ "style-loader", { "loader": "raw-loader" }, { "loader": "postcss-loader", "options": { "ident": "embedded", "plugins": postcssPlugins, "sourceMap": true } } ] }, { "include": [ path.join(process.cwd(), "src/styles.scss"), path.join(process.cwd(), "node_modules/plottable/plottable.css"), path.join(process.cwd(), "node_modules/snazzy-info-window/dist/snazzy-info-window.css") ], "test": /\.scss$|\.sass$/, "use": [ "style-loader", { "loader": "raw-loader" }, { "loader": "postcss-loader", "options": { "ident": "embedded", "plugins": postcssPlugins, "sourceMap": true } }, { "loader": "sass-loader", "options": { "sourceMap": true, "precision": 8, "includePaths": [] } } ] }, { "include": [ path.join(process.cwd(), "src/styles.scss"), path.join(process.cwd(), "node_modules/plottable/plottable.css"), path.join(process.cwd(), "node_modules/snazzy-info-window/dist/snazzy-info-window.css") ], "test": /\.less$/, "use": [ "style-loader", { "loader": "raw-loader" }, { "loader": "postcss-loader", "options": { "ident": "embedded", "plugins": postcssPlugins, "sourceMap": true } }, { "loader": "less-loader", "options": { "sourceMap": true } } ] }, { "include": [ path.join(process.cwd(), "src/styles.scss"), path.join(process.cwd(), "node_modules/plottable/plottable.css"), path.join(process.cwd(), "node_modules/snazzy-info-window/dist/snazzy-info-window.css") ], "test": /\.styl$/, "use": [ "style-loader", { "loader": "raw-loader" }, { "loader": "postcss-loader", "options": { "ident": "embedded", "plugins": postcssPlugins, "sourceMap": true } }, { "loader": "stylus-loader", "options": { "sourceMap": true, "paths": [] } } ] }, { "test": /\.ts$/, "loader": "@ngtools/webpack" } ] }, "plugins": [ new NoEmitOnErrorsPlugin(), new ScriptsWebpackPlugin({ "name": "scripts", "sourceMap": true, "filename": "scripts.bundle.js", "scripts": [ "/Users/artemstreltsov/source/spotter-clone/node_modules/jquery/dist/jquery.js", "/Users/artemstreltsov/source/spotter-clone/node_modules/hammerjs/hammer.js", "/Users/artemstreltsov/source/spotter-clone/node_modules/materialize-css/dist/js/materialize.js" ], "basePath": "/Users/artemstreltsov/source/spotter-clone" }), new CopyWebpackPlugin([ { "context": "src", "to": "", "from": { "glob": "assets/**/*", "dot": true } }, { "context": "src", "to": "", "from": { "glob": "favicon.ico", "dot": true } } ], { "ignore": [ ".gitkeep", "**/.DS_Store", "**/Thumbs.db" ], "debug": "warning" }), new ProgressPlugin(), new CircularDependencyPlugin({ "exclude": /(\\|\/)node_modules(\\|\/)/, "failOnError": false, "onDetected": false, "cwd": projectRoot }), new NamedLazyChunksWebpackPlugin(), new HtmlWebpackPlugin({ "template": "./src/index.html", "filename": "./index.html", "hash": false, "inject": true, "compile": true, "favicon": false, "minify": false, "cache": true, "showErrors": true, "chunks": "all", "excludeChunks": [], "title": "Webpack App", "xhtml": true, "chunksSortMode": function sort(left, right) { let leftIndex = entryPoints.indexOf(left.names[0]); let rightIndex = entryPoints.indexOf(right.names[0]); if (leftIndex > rightIndex) { return 1; } else if (leftIndex < rightIndex) { return -1; } else { return 0; } } }), new BaseHrefWebpackPlugin({}), new CommonsChunkPlugin({ "name": [ "inline" ], "minChunks": null }), new CommonsChunkPlugin({ "name": [ "vendor" ], "minChunks": (module) => { return module.resource && (module.resource.startsWith(nodeModules) || module.resource.startsWith(genDirNodeModules) || module.resource.startsWith(realNodeModules)); }, "chunks": [ "main" ] }), new SourceMapDevToolPlugin({ "filename": "[file].map[query]", "moduleFilenameTemplate": "[resource-path]", "fallbackModuleFilenameTemplate": "[resource-path]?[hash]", "sourceRoot": "webpack:///" }), new CommonsChunkPlugin({ "name": [ "main" ], "minChunks": 2, "async": "common" }), new NamedModulesPlugin({}), new AotPlugin({ "mainPath": "main.ts", "replaceExport": false, "hostReplacementPaths": { "environments/environment.ts": "environments/environment.ts" }, "sourceMap": true, "exclude": [], "tsConfigPath": "src/tsconfig.app.json", "skipCodeGeneration": true, "compilerOptions": {} }) ], "node": { "fs": "empty", "global": true, "crypto": "empty", "tls": "empty", "net": "empty", "process": true, "module": false, "clearImmediate": false, "setImmediate": false }, "devServer": { "historyApiFallback": true } }; ```
Webpack config prod. Created by `ng eject --prod` ```js const fs = require('fs'); const path = require('path'); const CopyWebpackPlugin = require('copy-webpack-plugin'); const ProgressPlugin = require('webpack/lib/ProgressPlugin'); const CircularDependencyPlugin = require('circular-dependency-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); const rxPaths = require('rxjs/_esm5/path-mapping'); const autoprefixer = require('autoprefixer'); const postcssUrl = require('postcss-url'); const postcssImports = require('postcss-import'); const { NoEmitOnErrorsPlugin, EnvironmentPlugin, HashedModuleIdsPlugin } = require('webpack'); const { ScriptsWebpackPlugin, BaseHrefWebpackPlugin, SuppressExtractedTextChunksWebpackPlugin, CleanCssWebpackPlugin, BundleBudgetPlugin, PostcssCliResources } = require('@angular/cli/plugins/webpack'); const { CommonsChunkPlugin, ModuleConcatenationPlugin } = require('webpack').optimize; const { LicenseWebpackPlugin } = require('license-webpack-plugin'); const { AotPlugin } = require('@ngtools/webpack'); const nodeModules = path.join(process.cwd(), 'node_modules'); const realNodeModules = fs.realpathSync(nodeModules); const genDirNodeModules = path.join(process.cwd(), 'src', '$$_gendir', 'node_modules'); const entryPoints = ["inline","polyfills","sw-register","styles","scripts","vendor","main"]; const hashFormat = {"chunk":".[chunkhash:20]","extract":".[contenthash:20]","file":".[hash:20]","script":".[hash:20]"}; const baseHref = ""; const deployUrl = ""; const projectRoot = process.cwd(); const maximumInlineSize = 10; const postcssPlugins = function (loader) { return [ postcssImports({ resolve: (url, context) => { return new Promise((resolve, reject) => { let hadTilde = false; if (url && url.startsWith('~')) { url = url.substr(1); hadTilde = true; } loader.resolve(context, (hadTilde ? '' : './') + url, (err, result) => { if (err) { if (hadTilde) { reject(err); return; } loader.resolve(context, url, (err, result) => { if (err) { reject(err); } else { resolve(result); } }); } else { resolve(result); } }); }); }, load: (filename) => { return new Promise((resolve, reject) => { loader.fs.readFile(filename, (err, data) => { if (err) { reject(err); return; } const content = data.toString(); resolve(content); }); }); } }), postcssUrl({ filter: ({ url }) => url.startsWith('~'), url: ({ url }) => { const fullPath = path.join(projectRoot, 'node_modules', url.substr(1)); return path.relative(loader.context, fullPath).replace(/\\/g, '/'); } }), postcssUrl([ { // Only convert root relative URLs, which CSS-Loader won't process into require(). filter: ({ url }) => url.startsWith('/') && !url.startsWith('//'), url: ({ url }) => { if (deployUrl.match(/:\/\//) || deployUrl.startsWith('/')) { // If deployUrl is absolute or root relative, ignore baseHref & use deployUrl as is. return `${deployUrl.replace(/\/$/, '')}${url}`; } else if (baseHref.match(/:\/\//)) { // If baseHref contains a scheme, include it as is. return baseHref.replace(/\/$/, '') + `/${deployUrl}/${url}`.replace(/\/\/+/g, '/'); } else { // Join together base-href, deploy-url and the original URL. // Also dedupe multiple slashes into single ones. return `/${baseHref}/${deployUrl}/${url}`.replace(/\/\/+/g, '/'); } } }, { // TODO: inline .cur if not supporting IE (use browserslist to check) filter: (asset) => { return maximumInlineSize > 0 && !asset.hash && !asset.absolutePath.endsWith('.cur'); }, url: 'inline', // NOTE: maxSize is in KB maxSize: maximumInlineSize, fallback: 'rebase', }, { url: 'rebase' }, ]), PostcssCliResources({ deployUrl: loader.loaders[loader.loaderIndex].options.ident == 'extracted' ? '' : deployUrl, loader, filename: `[name]${hashFormat.file}.[ext]`, }), autoprefixer({ grid: true }), ]; }; module.exports = { "resolve": { "extensions": [ ".ts", ".js" ], "symlinks": true, "modules": [ "./src", "./node_modules" ], "alias": rxPaths(), "mainFields": [ "browser", "module", "main" ] }, "resolveLoader": { "modules": [ "./node_modules", "./node_modules/@angular/cli/node_modules" ], "alias": rxPaths() }, "entry": { "main": [ "./src/main.ts" ], "polyfills": [ "./src/polyfills.ts" ], "styles": [ "./src/styles.scss", "./node_modules/plottable/plottable.css", "./node_modules/snazzy-info-window/dist/snazzy-info-window.css" ] }, "output": { "path": path.join(process.cwd(), "dist"), "filename": "[name].[chunkhash:20].bundle.js", "chunkFilename": "[id].[chunkhash:20].chunk.js", "crossOriginLoading": false }, "module": { "rules": [ { "test": /\.html$/, "loader": "raw-loader" }, { "test": /\.(eot|svg|cur)$/, "loader": "file-loader", "options": { "name": "[name].[hash:20].[ext]", "limit": 10000 } }, { "test": /\.(jpg|png|webp|gif|otf|ttf|woff|woff2|ani)$/, "loader": "url-loader", "options": { "name": "[name].[hash:20].[ext]", "limit": 10000 } }, { "exclude": [ path.join(process.cwd(), "src/styles.scss"), path.join(process.cwd(), "node_modules/plottable/plottable.css"), path.join(process.cwd(), "node_modules/snazzy-info-window/dist/snazzy-info-window.css") ], "test": /\.css$/, "use": [ { "loader": "raw-loader" }, { "loader": "postcss-loader", "options": { "ident": "embedded", "plugins": postcssPlugins, "sourceMap": false } } ] }, { "exclude": [ path.join(process.cwd(), "src/styles.scss"), path.join(process.cwd(), "node_modules/plottable/plottable.css"), path.join(process.cwd(), "node_modules/snazzy-info-window/dist/snazzy-info-window.css") ], "test": /\.scss$|\.sass$/, "use": [ { "loader": "raw-loader" }, { "loader": "postcss-loader", "options": { "ident": "embedded", "plugins": postcssPlugins, "sourceMap": false } }, { "loader": "sass-loader", "options": { "sourceMap": false, "precision": 8, "includePaths": [] } } ] }, { "exclude": [ path.join(process.cwd(), "src/styles.scss"), path.join(process.cwd(), "node_modules/plottable/plottable.css"), path.join(process.cwd(), "node_modules/snazzy-info-window/dist/snazzy-info-window.css") ], "test": /\.less$/, "use": [ { "loader": "raw-loader" }, { "loader": "postcss-loader", "options": { "ident": "embedded", "plugins": postcssPlugins, "sourceMap": false } }, { "loader": "less-loader", "options": { "sourceMap": false } } ] }, { "exclude": [ path.join(process.cwd(), "src/styles.scss"), path.join(process.cwd(), "node_modules/plottable/plottable.css"), path.join(process.cwd(), "node_modules/snazzy-info-window/dist/snazzy-info-window.css") ], "test": /\.styl$/, "use": [ { "loader": "raw-loader" }, { "loader": "postcss-loader", "options": { "ident": "embedded", "plugins": postcssPlugins, "sourceMap": false } }, { "loader": "stylus-loader", "options": { "sourceMap": false, "paths": [] } } ] }, { "include": [ path.join(process.cwd(), "src/styles.scss"), path.join(process.cwd(), "node_modules/plottable/plottable.css"), path.join(process.cwd(), "node_modules/snazzy-info-window/dist/snazzy-info-window.css") ], "test": /\.css$/, "loaders": ExtractTextPlugin.extract({ "use": [ { "loader": "raw-loader" }, { "loader": "postcss-loader", "options": { "ident": "extracted", "plugins": postcssPlugins, "sourceMap": false } } ], "publicPath": "" }) }, { "include": [ path.join(process.cwd(), "src/styles.scss"), path.join(process.cwd(), "node_modules/plottable/plottable.css"), path.join(process.cwd(), "node_modules/snazzy-info-window/dist/snazzy-info-window.css") ], "test": /\.scss$|\.sass$/, "loaders": ExtractTextPlugin.extract({ "use": [ { "loader": "raw-loader" }, { "loader": "postcss-loader", "options": { "ident": "extracted", "plugins": postcssPlugins, "sourceMap": false } }, { "loader": "sass-loader", "options": { "sourceMap": false, "precision": 8, "includePaths": [] } } ], "publicPath": "" }) }, { "include": [ path.join(process.cwd(), "src/styles.scss"), path.join(process.cwd(), "node_modules/plottable/plottable.css"), path.join(process.cwd(), "node_modules/snazzy-info-window/dist/snazzy-info-window.css") ], "test": /\.less$/, "loaders": ExtractTextPlugin.extract({ "use": [ { "loader": "raw-loader" }, { "loader": "postcss-loader", "options": { "ident": "extracted", "plugins": postcssPlugins, "sourceMap": false } }, { "loader": "less-loader", "options": { "sourceMap": false } } ], "publicPath": "" }) }, { "include": [ path.join(process.cwd(), "src/styles.scss"), path.join(process.cwd(), "node_modules/plottable/plottable.css"), path.join(process.cwd(), "node_modules/snazzy-info-window/dist/snazzy-info-window.css") ], "test": /\.styl$/, "loaders": ExtractTextPlugin.extract({ "use": [ { "loader": "raw-loader" }, { "loader": "postcss-loader", "options": { "ident": "extracted", "plugins": postcssPlugins, "sourceMap": false } }, { "loader": "stylus-loader", "options": { "sourceMap": false, "paths": [] } } ], "publicPath": "" }) }, { "test": /\.ts$/, "use": [ "@ngtools/webpack" ] } ] }, "plugins": [ new NoEmitOnErrorsPlugin(), new ScriptsWebpackPlugin({ "name": "scripts", "sourceMap": false, "filename": "scripts.[hash:20].bundle.js", "scripts": [ "/Users/artemstreltsov/source/spotter-clone/node_modules/jquery/dist/jquery.js", "/Users/artemstreltsov/source/spotter-clone/node_modules/hammerjs/hammer.js", "/Users/artemstreltsov/source/spotter-clone/node_modules/materialize-css/dist/js/materialize.js" ], "basePath": "/Users/artemstreltsov/source/spotter-clone" }), new CopyWebpackPlugin([ { "context": "src", "to": "", "from": { "glob": "assets/**/*", "dot": true } }, { "context": "src", "to": "", "from": { "glob": "favicon.ico", "dot": true } } ], { "ignore": [ ".gitkeep", "**/.DS_Store", "**/Thumbs.db" ], "debug": "warning" }), new ProgressPlugin(), new CircularDependencyPlugin({ "exclude": /(\\|\/)node_modules(\\|\/)/, "failOnError": false, "onDetected": false, "cwd": projectRoot }), new HtmlWebpackPlugin({ "template": "./src/index.html", "filename": "./index.html", "hash": false, "inject": true, "compile": true, "favicon": false, "minify": { "caseSensitive": true, "collapseWhitespace": true, "keepClosingSlash": true }, "cache": true, "showErrors": true, "chunks": "all", "excludeChunks": [], "title": "Webpack App", "xhtml": true, "chunksSortMode": function sort(left, right) { let leftIndex = entryPoints.indexOf(left.names[0]); let rightIndex = entryPoints.indexOf(right.names[0]); if (leftIndex > rightIndex) { return 1; } else if (leftIndex < rightIndex) { return -1; } else { return 0; } } }), new BaseHrefWebpackPlugin({}), new CommonsChunkPlugin({ "name": [ "inline" ], "minChunks": null }), new CommonsChunkPlugin({ "name": [ "vendor" ], "minChunks": (module) => { return module.resource && (module.resource.startsWith(nodeModules) || module.resource.startsWith(genDirNodeModules) || module.resource.startsWith(realNodeModules)); }, "chunks": [ "main" ] }), new CommonsChunkPlugin({ "name": [ "main" ], "minChunks": 2, "async": "common" }), new ExtractTextPlugin({ "filename": "[name].[contenthash:20].bundle.css" }), new SuppressExtractedTextChunksWebpackPlugin(), new CleanCssWebpackPlugin(), new EnvironmentPlugin({ "NODE_ENV": "production" }), new HashedModuleIdsPlugin({ "hashFunction": "md5", "hashDigest": "base64", "hashDigestLength": 4 }), new ModuleConcatenationPlugin({}), new BundleBudgetPlugin({}), new LicenseWebpackPlugin({ "licenseFilenames": [ "LICENSE", "LICENSE.md", "LICENSE.txt", "license", "license.md", "license.txt", "LICENCE", "LICENCE.md", "LICENCE.txt", "licence", "licence.md", "licence.txt" ], "perChunkOutput": false, "outputTemplate": path.join(process.cwd(), "node_modules/license-webpack-plugin/output.template.ejs"), "outputFilename": "3rdpartylicenses.txt", "suppressErrors": true, "includePackagesWithoutLicense": false, "abortOnUnacceptableLicense": false, "addBanner": false, "bannerTemplate": "/*! 3rd party license information is available at <%- filename %> */", "includedChunks": [], "excludedChunks": [], "additionalPackages": [], "modulesDirectories": [ "node_modules" ], "pattern": /^(MIT|ISC|BSD.*)$/ }), new UglifyJsPlugin({ "test": /\.js(\?.*)?$/i, "extractComments": false, "sourceMap": false, "cache": true, "parallel": true, "uglifyOptions": { "compress": { "typeofs": false, "inline": 3 }, "output": { "ascii_only": true, "comments": false, "webkit": true }, "ecma": 5, "warnings": false, "ie8": false, "mangle": { "safari10": true } } }), new AotPlugin({ "mainPath": "main.ts", "replaceExport": false, "hostReplacementPaths": { "environments/environment.ts": "environments/environment.prod.ts" }, "sourceMap": false, "exclude": [], "tsConfigPath": "src/tsconfig.app.json", "compilerOptions": {} }) ], "node": { "fs": "empty", "global": true, "crypto": "empty", "tls": "empty", "net": "empty", "process": true, "module": false, "clearImmediate": false, "setImmediate": false }, "devServer": { "historyApiFallback": true } }; ```

Impact

This issue with documentation and missing setup steps is probably also affecting Angular 5 because there is no angular.json file there either. The first angular with angular.json is Angular 6

If our efforts are successful here, the documentation should probably be updated as well

Environment


Libs:
- "@angular/core": "^4.4.0", // v4.4.7 in package-lock.json
-  "single-spa-angular": "^3.6.0",
- "@angular/cli": "^1.7.4"
- "typescript": "~2.3.3"
-  "webpack": "^3.5.4" // before eject,

After eject:
    "uglifyjs-webpack-plugin": "^1.1.8",
    "webpack": "~3.11.0",
    "webpack-dev-server": "~2.11.0",
    "@angular-devkit/core": "0.3.2",
    "@ngtools/webpack": "1.10.2",
    "autoprefixer": "^7.2.3",
    "file-loader": "^1.1.5",
    "html-webpack-plugin": "^2.29.0",
    "less-loader": "^4.0.5",
    "postcss-import": "^11.0.0",
    "postcss-loader": "^2.0.10",
    "postcss-url": "^7.1.2",
    "raw-loader": "^0.5.1",
    "sass-loader": "^6.0.6",
    "istanbul-instrumenter-loader": "^3.0.0",
    "style-loader": "^0.19.1",
    "stylus-loader": "^3.0.1",
    "url-loader": "^0.6.2",
    "circular-dependency-plugin": "^4.2.1",
    "copy-webpack-plugin": "~4.4.1"


Others:
- node v10.24.1

MilanKovacic commented 11 months ago

As we have discussed in the Slack channel, you have two options:

Process of updating webpack configuration to make it compatible with single-spa typically consists of:

This process transforms the application to a library, so that it can be orchestrated by single-spa.

To see what has to be changed, take a look here:

https://github.com/single-spa/single-spa-angular/tree/3.x/src/builders https://github.com/single-spa/single-spa-angular/blob/3.x/src/webpack/index.ts

arturovt commented 11 months ago

@arstrel I began maintaining this project in 2020 when it was already using Angular version 9. This means I was unaware of how the project would be compatible with earlier Angular versions, as its maintenance had been discontinued many years ago. I'll attempt to regain access to my Slack account in the single-spa workspace so that we can chat there. It might still be challenging to understand what's happening on your side until I can review the actual code.

Please also note that old single-spa-angular versions are unreliable and have a lot of issues that were fixed in next versions.