Skip to content

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

@r4m-davronu

Description

@r4m-davronu

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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions