single-spa / single-spa-vue

a single-spa plugin for vue.js applications
MIT License
182 stars 42 forks source link

Need help with single-spa-vue and webpack5 #116

Open r4m-davronu opened 6 months ago

r4m-davronu commented 6 months ago

I have 2 applications for my single-spa

First application webpack.config.js:

{
        mode: vars.webpackMode,
        entry: options.webpackEntries,
        experiments: {
            outputModule: true,
        },
        output: {
            path: path.resolve(options.appPath, buildDirectory, vars.publicSubDir),
            publicPath: vars.publicPath,
            filename: vars.prodBuild ? '[name].[contenthash:8].js' : 'js/[name].js',
            module: true
        },
        cache: vars.prodBuild ? false : {
            type: 'memory',
            cacheUnaffected: true,
        },
        snapshot: {
            managedPaths: [/^(.+?[\\/]node_modules)[\\/]((?!@route4me)).*[\\/]*/],
        },
        target: ['web', 'browserslist'],
        externals: { ...options.webpackExternal },
        resolve: {
            alias: options.webpackAliases,
            extensions: [
                '.ts',
                '.js',
                '.vue',
                '.json',
            ],
        },
        module: {
            rules: [
                // es
                {
                    test: /\.m?js$/,
                    exclude(modulePath) {
                        const es6sourcesRegExpArray = [
                            /r4m-shared-ui[\\/]src/,
                            /node_modules[\\/]vuetify/,
                        ];
                        if (es6sourcesRegExpArray.filter(es6path => es6path.test(modulePath)).length > 0) {
                            return false;
                        }
                        return /(node_modules|\.min\.)/.test(modulePath);
                    },
                    use: [
                        {
                            loader: 'babel-loader',
                            options: {
                                presets: [
                                    [
                                        '@babel/preset-env',
                                    ],
                                ],
                                plugins: [
                                    '@babel/plugin-proposal-object-rest-spread',
                                    '@babel/plugin-syntax-dynamic-import',
                                    '@babel/plugin-transform-parameters',
                                ],
                                cacheDirectory: true,
                            },
                        },
                    ],
                },
                {
                    test: /\.(ts)$/,
                    loader: 'ts-loader',
                    options: {
                        appendTsSuffixTo: [/\.vue$/],
                        allowTsInNodeModules: true,
                        context: path.resolve(options.appPath),
                        compilerOptions: {
                            outDir: path.resolve(options.appPath, buildDirectory, vars.publicSubDir),
                        },
                    },
                },
                // VUE
                {
                    test: /\.vue$/,
                    loader: 'vue-loader',
                    options: {
                        loader: {
                            scss: 'vue-style-loader!css-loader!sass-loader',
                        },
                    },
                },
                // CSS & SCSS
                {
                    test: /\.(css|scss)$/,
                    use: [
                        MiniCssExtractPlugin.loader, 

                        {
                            loader: 'css-loader',
                            options: {
                                importLoaders: 1,
                                sourceMap: true,
                            },
                        },
                        {
                            loader: 'postcss-loader',
                            options: {
                                postcssOptions: {
                                    plugins: [
                                        require('autoprefixer'),
                                    ],
                                },
                            },
                        },
                        {
                            loader: 'sass-loader',
                            options: sassLoaderOptions,
                        },
                    ],
                },
                // WOFF FONTS
                {
                    test: /\.(woff(2)?|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/,
                    loader: 'file-loader',
                    options: {
                        name: vars.prodBuild ? '[name].[contenthash:8].[ext]' : '[name].[ext]',
                        outputPath: 'fonts/',
                    },
                },
                // IMAGES
                {
                    test: /\.(png|svg|jpe?g|gif)$/,
                    loader: 'file-loader',
                    options: {
                        name: vars.prodBuild ? '[name].[hash].[ext]' : '[name].[ext]',
                        outputPath: 'img/',
                        // publicPath: 'img/', // don't override, it's ok for referencing from CSS/SCSS ONLY! Not JS/Vue!
                    },
                },
            ],
        },
        plugins: [
            new MiniCssExtractPlugin({
                // Options similar to the same options in webpackOptions.output
                // both options are optional
                filename: vars.prodBuild ? '[name].[contenthash:8].css' : '[name].css',
                // chunkFilename: vars.prodBuild ? '[id].[contenthash:8].css' : '[id].css',
            }),

            new VueLoaderPlugin({
                optimizeSSR: false,
            }),

            new VuetifyLoaderPlugin(),

            new AssetsPlugin({
                path: path.resolve(options.appPath, 'storage', 'json'),
                filename: 'assets.json',
                prettyPrint: true,
                entrypoints: true,
            }),

            vars.prodBuild && new BundleAnalyzerPlugin({
                analyzerMode: 'static',
                reportFilename: path.resolve(options.appPath, 'bundle_report.html'),
                openAnalyzer: false,
            }),

            vars.hmr ? new webpack.HotModuleReplacementPlugin() : undefined,
        ].filter(Boolean),

        devtool: vars.prodBuild ? 'nosources-source-map' : 'eval-source-map',
        devServer: {
            static: {
                directory: options.appPath,
            },
            devMiddleware: {
                writeToDisk: true,
            },
            server: vars.wdsUrl.match(/https:/i) ? {
                type: 'https',
                options:  {
                    cert: './ssl.crt',
                    key: './ssl.key',
                },
            } : false,
            allowedHosts: "all",
            port: vars.wdsPort,
            compress: true,
            client: {
                overlay: false,
            },
            headers: {
                'Access-Control-Allow-Origin': vars.appUrl,
                'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
                'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization',
                'Access-Control-Allow-Credentials': 'true',
            },
        }
    }

First application main.js:

const vueLifecycles = singleSpaVue({
  Vue,
  appOptions: {
    render: (h) => h(App),
  },
});

export const bootstrap = vueLifecycles.bootstrap;
export const mount = vueLifecycles.mount;
export const unmount = vueLifecycles.unmount;```

First application package.json:
{
  "private": false,
  "scripts": {
    "start": "npm run serve",
    "build": "webpack",
    "serve": "webpack-dev-server",
    "create_env_webpack": "echo \"No need\" && exit 0",
    "test": "echo \"Error: no test specified\" && exit 1",
    "translate": "node node_modules/r4m-shared-ui/src/utils/translate-json.js",
    "lint": "eslint \"src/**/*.{js,vue}\" --fix"
  },
  "engines": {
    "node": ">=14.0.0 <15.0.0",
    "npm": ">=6.0.0 <7.0.0",
    "yarn": "forbidden"
  },
  "author": "",
  "license": "license.md",
  "dependencies": {
    "@mdi/font": "^5.9.55",
    "@mdi/js": "^5.9.55",
    "@vue/composition-api": "^1.7.2",
    "axios": "^0.26.1",
    "dayjs": "^1.11.10",
    "laravel-echo": "^1.8.1",
    "lodash": "^4.17.21",
    "promise-polyfill": "^8.1.3",
    "pusher-js": "^7.0.0",
    "qs": "^6.9.4",
    "single-spa": "^6.0.1",
    "sweetalert2": "^8.16.3",
    "systemjs-webpack-interop": "^2.3.7",
    "throttle-debounce": "^5.0.0",
    "vue": "2.6.14",
    "vue-axios": "^2.1.5",
    "vue-class-component": "^7.2.6",
    "vue-i18n": "^8.28.2",
    "vue-property-decorator": "^8.5.1",
    "vue2-perfect-scrollbar": "^1.5.56",
    "vuetify": "^2.1.9",
    "ws": "^8.16.0"
  },
  "devDependencies": {
    "@babel/core": "^7.5.5",
    "@babel/plugin-proposal-object-rest-spread": "^7.5.5",
    "@babel/plugin-syntax-dynamic-import": "^7.2.0",
    "@babel/preset-env": "^7.5.5",
    "@types/lodash": "^4.14.150",
    "@types/node": "^16.9.2",
    "@types/throttle-debounce": "^5.0.2",
    "@typescript-eslint/eslint-plugin": "^2.34.0",
    "@typescript-eslint/parser": "^2.34.0",
    "@vue/eslint-config-typescript": "^5.1.0",
    "assets-webpack-plugin": "^7.1.1",
    "autoprefixer": "^9.7.3",
    "babel-eslint": "^10.0.3",
    "babel-loader": "^8.0.6",
    "browserslist": "^4.23.0",
    "clean-webpack-plugin": "^3.0.0",
    "css-loader": "^2.1.1",
    "css-minimizer-webpack-plugin": "^6.0.0",
    "dotenv-flow": "^3.1.0",
    "eslint": "^5.16.0",
    "eslint-config-airbnb-base": "^13.2.0",
    "eslint-config-vuetify": "^0.4.0",
    "eslint-import-resolver-webpack": "^0.11.1",
    "eslint-loader": "^2.2.1",
    "eslint-plugin-import": "^2.18.2",
    "eslint-plugin-vue": "^5.2.3",
    "eslint-plugin-vuetify": "^1.0.0-beta.5",
    "file-loader": "^4.2.0",
    "fs": "0.0.1-security",
    "html-webpack-plugin": "^5.6.0",
    "mini-css-extract-plugin": "^0.7.0",
    "node-fetch": "^2.6.7",
    "optimize-css-assets-webpack-plugin": "^5.0.3",
    "postcss-loader": "^3.0.0",
    "sass": "~1.32.0",
    "sass-loader": "^8.0.0",
    "style-loader": "^1.0.0",
    "terser-webpack-plugin": "^5.3.10",
    "ts-loader": "^9.5.1",
    "typescript": "^4.9.5",
    "url-loader": "^1.1.2",
    "v-mask": "^2.3.0",
    "vue-eslint-parser": "^6.0.4",
    "vue-loader": "15.9.2",
    "vue-style-loader": "^4.1.2",
    "vue-template-compiler": "2.6.14",
    "vuetify-loader": "1.5.0",
    "webpack": "^5.90.3",
    "webpack-bundle-analyzer": "^4.10.1",
    "webpack-cli": "^5.1.4",
    "webpack-dev-server": "^5.0.2"
  },
  "babel": {
    "presets": [
      "@babel/preset-env"
    ]
  }
}

bootstrap, mount, unmount - exists on main.ts at all.

But when i add this application to single-spa like this:

<script type="systemjs-importmap">
      {
        "imports": {
           ...
          "app1": "http://localhost:9000/js/app.js",
          ...
        }
      }
    </script>  
...
...
...
singleSpa.registerApplication(
            'app1',
            async () => {
                const sc = await System.import('app1');
                console.log('first application', sc)
                return System.import('app1')
            },
            location => true
          )

console returns me not Module, only error: Uncaught app1: Application 'app1' died in status LOADING_SOURCE_CODE: "does not export an unmount function or array of functions"

Second application with boilerplate vue-cli works perfectly.

How to fix my application on webpack?

r4m-davronu commented 6 months ago

image look at screen (don't pay attention to the app name)

r4m-davronu commented 6 months ago

@joeldenning look please