arcadeJHS / AngularVueIntegration

An opinionated attempt to let Angular 1.x and Vue peacefully live together
29 stars 7 forks source link

What would be the proper way to integrate your Vue build with a legacy AngularJS app using Webpack? #1

Closed sbefort closed 11 months ago

sbefort commented 5 years ago

This is really a nice tutorial about how to migrate AngularJS to Vue. I appreciate the tags you setup on your repository to easily move from step to step.

Everything works as expected when integrating Vue with a non-webpack AngularJS app. But what would be the major differences when integrating a Vue app using webpack with an AngularJS app using webpack? I tried to import the Vue build files into my Angular app, but I get a console error that states:

Cannot assign to read only property 'exports' of object '#<Object>' 

Thank you for your help.

arcadeJHS commented 5 years ago

Well, currently not working on this, so I have no specific examples to point you at, sorry... Could you elaborate more on what's are you trying to achieve, what's your app setup, webpack version and so on? Maybe a wrong webpack configuration? Or some sort of wrong chain of require/import/exports? Something not rightly transpiled by babel? Something usefull looking for "Cannot assign to read only property 'exports' of object '#'" on Goole?

strongSoda commented 4 years ago

@arcadeJHS I have an Angular1.6 app using webpack and I am trying to set up your Vue set up in it. When I try to include the dist scripts in index.html of angular, I get a 404 for all scripts.

How do I bring these in my angular setup?

index.html Screenshot from 2020-10-23 15-58-21

Error Screenshot from 2020-10-23 16-01-10

Directory Structure Screenshot from 2020-10-23 16-05-11

Angular webpack.config.js

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 UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const {NoEmitOnErrorsPlugin, SourceMapDevToolPlugin, DefinePlugin} = require('webpack');
const {CommonsChunkPlugin} = require('webpack').optimize;
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
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", "vendor", "main"];
const projectRoot = process.cwd();
const CompressionPlugin = require("compression-webpack-plugin");
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const extractCSS = new ExtractTextPlugin('[name].[hash:6].css');
const SentryCliPlugin = require('@sentry/webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
let gitTag = require('child_process').execSync('git describe --tags `git rev-list --tags --max-count=1`').toString().trim().replace(/[R|D]./g, "");

// the path(s) that should be cleaned
let pathsToClean = [
  'prodDist'
];

// the clean options to use
let cleanOptions = {
  beforeEmit: true,/// perform clean just before files are emitted to the output dir
  verbose: true,
  dry: false
};

module.exports = {
  "resolve": {
    "extensions": [
      ".ts",
      ".js"
    ],
    "symlinks": true,
    "modules": [
      "./src",
      "./node_modules"
    ],
    "mainFields": ["browser", "module", "main"]
  },
  "resolveLoader": {
    "modules": [
      "./node_modules"
    ],
  },
  /*
   * Entry - An entry point indicates which module webpack should use to begin building out its internal dependency graph.
   * After entering the entry point, webpack will figure out which other modules and libraries that entry point depends on (directly and indirectly).
   * Every dependency is then processed and outputted into files called bundle
   * Entry for main, polyfills and styles
   * This tells webpack to create dependency graphs starting main.ts, polyfills.ts, styles.less, styless.scss.
   * These graphs are completely separate and independent of each other (there will be a webpack bootstrap in each bundle)
  */
  "entry": {
    "main": [
      "./src/app/app.module.ts"
    ],
    /* polyfill is a browser fallback, made in JavaScript, that allows functionality you expect to work in modern browsers to work in older browsers,*/
    "polyfills": [
      "./src/polyfills.ts"
    ],
    "styles": [
      "./src/styles.less",
      "./src/styles.scss"
    ]
  },
  /*
    If your configuration creates more than a single "chunk" (as with multiple entry points or when using plugins
    like CommonsChunkPlugin), you should use substitutions to ensure that each file has a unique name in the output.
  */
  "output": {
    "path": path.join(process.cwd(), "prodDist"),
    "filename": "[name].bundle.[chunkhash:6].js",
    "chunkFilename": "[id].chunk.js",
    "crossOriginLoading": false,
  },

  "module": {
    /*
 *  module.rules allows you to specify several loaders within   your webpack configuration.
 *  webpack only understands JavaScript files. Loaders allow webpack to process other
 *  types of files and converting them into valid modules that can be consumed by your
 *  application and added to the dependency graph.
 *  The test property identifies which file or files should be transformed
 *  The use property indicates which loader should be used to do the transforming
 *  These rules can modify how the module is created. They can apply loaders to the module, or modify the parser.
*/
    "rules": [
      {
        "test": /\.html$/,  // it looks for all files that end with .html
        use: {
          "loader": "raw-loader"
        }//raw-loader A loader for webpack that allows importing files as a String.
      },
      {
        "test": /\.(eot|cur|svg)$/, // for files ending with .eot .svg or .cur file-loader is used
        "loader": "file-loader",    //Instructs webpack to emit the required object as file and to return its public URL
        "options": {
          "name": "[name].[hash:20].[ext]", //Configure a custom filename template for your file
          "limit": 10000
        }
      },
      {
        "test": /\.(jpg|png|webp|gif|otf|ttf|woff|woff2|ani|svg)$/, // it will look for files with given extnsions
        "loader": "url-loader",  //Webpack can inline assets by using url-loader. It emits your images as base64 strings within your JavaScript bundles
        "options": {
          "name": "[name].[hash:20].[ext]",
          "limit": 10000
        }
      },
      /*
      * When multiple loaders are chained, it is important to remember that they are executed in reverse order
      * -- either right to left or bottom to top depending on array format.
      * The last loader, called first, will be passed the contents of the raw resource.
      * The first loader, called last, is expected to return JavaScript and an optional source map.
      * The loaders in between will be executed with the result(s) of the previous loader in the chain.*/
      {
        "include": [
          path.join(process.cwd(), "src/styles.scss") // it says, consider this file also
        ],
        "test": /\.css$/, // and look for all files that end with .css
        use: extractCSS.extract({
          fallback: 'style-loader',
          use: [
            {
              loader: 'css-loader',
              options: {
                // If you are having trouble with urls not resolving add this setting.
                // See https://github.com/webpack-contrib/css-loader#url
                url: false,
                minimize: true,
                sourceMap: true
              }
            },

          ]
        })
      },
      {
        "include": [
          path.join(process.cwd(), "src/")
        ],
        "test": /\.scss$|\.sass$/,
        use: extractCSS.extract({
          fallback: 'style-loader',
          use: [
            {
              loader: 'css-loader',
              options: {
                // If you are having trouble with urls not resolving add this setting.
                // See https://github.com/webpack-contrib/css-loader#url
                url: false,
                minimize: true,
                sourceMap: true
              }
            },
            {
              loader: 'sass-loader',
              options: {
                sourceMap: true
              }
            }

          ]
        })
      },
      {
        "include": [
          path.join(process.cwd(), "src/styles.less")
        ],
        "test": /\.less$/,
        use: extractCSS.extract({
          fallback: 'style-loader',
          use: [
            {
              loader: 'css-loader',
              options: {
                // If you are having trouble with urls not resolving add this setting.
                // See https://github.com/webpack-contrib/css-loader#url
                url: false,
                minimize: true,
                sourceMap: true
              }
            },
            {
              loader: 'less-loader',
              options: {
                sourceMap: true
              }
            }
          ]
        })

      },
      {
        "test": /\.ts$/,  // check for .ts files
        use: [
          {
            "loader": "ts-loader", // This is the typescript loader for webpack.
            options: {
              transpileOnly: true,
              compilerOptions: {}
              /*If you want to speed up compilation significantly you can set this flag
              However, many of the benefits you get from static type checking between different dependencies in your application will be lost.*/

            }
          }]
      }

    ]
  },

  /* A webpack plugin is a JavaScript object that has an apply property.
   This apply property is called by the webpack compiler, giving access
   to the entire compilation lifecycle.Since plugins can take arguments/options,
    you must pass a new instance to the plugins property in your webpack configuration.*/
  "plugins": [

    new DefinePlugin({
      'process.env.gitTag': JSON.stringify(gitTag),
      'process.env.NODE_ENV': JSON.stringify('production'),

    }),

    extractCSS,

    /*Use the NoEmitOnErrorsPlugin to skip the emitting phase whenever there are errors while compiling.
    This ensures that no assets are emitted that include errors.*/
    new NoEmitOnErrorsPlugin(),
    /*Copies individual files or entire directories to the build directory. new CopyWebpackPlugin([ ...patterns ], options)*/

    new CopyWebpackPlugin([
        {
          "context": "src",
          "to": path.join(process.cwd(), "prodDist"), // destination not given
          "from": {
            "glob": "assets/**/*",
            "dot": true
          }
        },
        {
          "context": "src",
          "to": path.join(process.cwd(), "prodDist"), // destination not given
          "from": {
            "glob": "favicon.ico",
            "dot": true
          }
        }
      ],
      {
        "ignore": [
          ".gitkeep",
          "**/.DS_Store",
          "**/Thumbs.db"
        ],
        "debug": "warning" // its default value
      }),

    /* an internal webpack plugin .You should only care about them if you are building a own compiler based on webpack, or introspect the internals.
    * Hook into the compiler to extract progress information. The handler must have the signature function(percentage, message).
    * It's called with 0 <= percentage <= 1. percentage == 0 indicates the start. percentage == 1 indicates the end.
    */
    new ProgressPlugin(),

    /*Detect modules with circular dependencies when bundling with webpack.*/
    new CircularDependencyPlugin({
      // exclude detection of files based on a RegExp
      "exclude": /(\\|\/)node_modules(\\|\/)/,
      // add errors to webpack instead of warnings when failOnError set to true
      "failOnError": false,
      "onDetected": false,
      // set the current working directory for displaying module paths
      "cwd": projectRoot
    }),

    /*Assigns names to the async chunks generated by Webpack for lazy routes in an Angular app.*/
    //new NamedLazyChunksWebpackPlugin(),

    /*Plugin that simplifies creation of HTML files to serve your bundles*/
    new HtmlWebpackPlugin({
      "template": "./src/index.html", //path to the template wich acts as source
      "filename": "./index.html",  //The file to write the HTML to
      "hash": false, /* If true then append a unique webpack compilation hash to all included scripts and CSS files.
                        This is useful for cache busting*/
      "inject": true, /*Inject all assets into the given template or templateContent. When passing true or 'body' all
                       javascript resources will be placed at the bottom of the body element. 'head' will place the scripts in the head element*/
      "compile": true,
      "favicon": false, /*Adds the given favicon path to the output HTML*/
      "minify": {
        collapseWhitespace: true,
        html5: true,
        minifyCSS: true,
        removeComments: true,
        minifyJS: true,

      }, /*Pass html-minifier's options as object to minify the output. https://github.com/kangax/html-minifier#options-quick-reference*/
      "cache": true, /* Emit the file only if it was changed*/
      "showErrors": true, /*Errors details will be written into the HTML page*/
      "chunks": "all", /*Allows you to add only some chunks */
      "excludeChunks": [],
      "title": "Webpack App",//The title to use for the generated HTML document
      "xhtml": true, //If true render the link tags as self-closing
      /*Allows to control how chunks should be sorted before they are included to the HTML.
        Allowed values are 'none' | 'auto' | 'dependency' | 'manual' | {Function} */
      "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;
        }
      }
    }),

    /*Extension for html-webpack-plugin to programmatically insert or update <base href="" /> tag.*/
    /*Plugin leaves your template untouched if baseHref option is not provided*/
    //Note: with base href you can specify a base URL for all relative URLs on a page:
    //new BaseHrefWebpackPlugin({}),

    /*The CommonsChunkPlugin is an opt-in feature that creates a separate file (known as a chunk),
    consisting of common modules shared between multiple entry points.*/
    new CommonsChunkPlugin({
      "name": [
        "inline"
      ],
      "minChunks": null //The minimum number of chunks which need to contain a module before it's moved into the commons chunk.
    }),

    new CommonsChunkPlugin({
      "name": [
        "vendor"
      ],
      "minChunks": (module) => {
        return module.resource
          && (module.resource.startsWith(nodeModules)
            || module.resource.startsWith(genDirNodeModules)
            || module.resource.startsWith(realNodeModules));
      },
      "chunks": [ // Select the source chunks by chunk names. The chunk must be a child of the commons chunk.If omitted all entry chunks are selected.
        "main"
      ]
    }),

    /*This plugin enables more fine grained control of source map generation*/
    new SourceMapDevToolPlugin({
      "filename": "sourceMaps/[file].map[query]", //Defines the output filename of the SourceMap (will be inlined if no value is provided).
      "moduleFilenameTemplate": "[resource-path]",//[resource-path]- The path used to resolve the file without any query params
      "fallbackModuleFilenameTemplate": "[resource-path]?[hash]",//it is used If multiple modules would result in the same name
      "sourceRoot": "webpack:///",
    }),

    new CommonsChunkPlugin({
      "name": [
        "main"
      ],
      "minChunks": 2,
      "async": "common"
    }),

    /*This plugin will cause the relative path of the module to be displayed when HMR is enabled.
     Hot Module Replacement allows all kinds of modules to be updated at runtime without the need for a full refresh*/
    //new NamedModulesPlugin({}),

    //Webpack plugin that runs typescript type checker on a separate process.
    /*As your project becomes bigger, compilation time increases linearly.
     It's because typescript's semantic checker has to inspect all files on every rebuild.
     The simple solution is to disable it by using the transpileOnly: true option, but doing so leaves you without type checking.
     You probably don't want to give up type checking; that's rather the point of TypeScript.
     So what you can do is use the fork-ts-checker-webpack-plugin.
     It runs the type checker on a separate process,
     so your build remains fast thanks to transpileOnly: true but you still have the type checking. */
    new ForkTsCheckerWebpackPlugin({
      "tsconfig": "./tsconfig.json"
    }),
    //new ngminPlugin(),
    new UglifyJsPlugin({
      sourceMap: true,
      uglifyOptions: {
        compress: true,
        mangle: true
      },
      parallel:true
    }),
    new CompressionPlugin({
      asset: "[path].gz[query]",
      algorithm: "gzip",
      test: /\.js$|\.css$|\.json$|\.js.map$|\.xml$|\.scss$/,
      threshold: 10240,
      minRatio: 0.8,
    }),
    new SentryCliPlugin({
      include: './prodDist',
      release: gitTag,
      ignoreFile: '.sentrycliignore',
      organization: 'mammoth-analytics-inc',
      ignore: ['node_modules', 'webpack.config.js'],

    }),
    new CleanWebpackPlugin(pathsToClean, cleanOptions)
    //new BundleAnalyzerPlugin()
  ],

  /*These options configure whether to polyfill or mock certain Node.js globals and modules.
  This allows code originally written for the Node.js environment to run in other environments like the browser.
  true: Provide a polyfill.
  "mock": Provide a mock that implements the expected interface but has little or no functionality
  "empty": Provide an empty object.
  false: Provide nothing.*/
  "node": {
    "fs": "empty",
    "global": true,
    "crypto": "empty",
    "tls": "empty",
    "net": "empty",
    "process": true,//default
    "module": false,
    "clearImmediate": false,
    "setImmediate": false
  },

};

Angular package.json

"scripts": {
    "ng": "ng",
    "start": "webpack-dev-server --port=3000 --config=webpack.dev.config.js",
    "build": "node --max_old_space_size=4096  node_modules/.bin/webpack --config=webpack.config.js  ",
    "test-karma": "karma start ./karma.conf.js",
    "test": "jest",
    "test:watch": "jest --watch",
    "test:ci": "jest --runInBand",
    "lint": "ng lint",
    "e2e": "protractor ./protractor.conf.js",
    "packagr": "ng-packagr -p ng-package.json",
    "pree2e": "webdriver-manager update --standalone false --gecko false --quiet"
  },
  "keywords": [
    "express",
    "gulp",
    "browserify",
    "angular",
    "sass",
    "karma",
    "jasmine",
    "protractor",
    "boilerplate"
  ],
  "private": true,
  "engines": {
    "node": ">=0.12.x"
  },
  "browserify": {
    "transform": [
      "aliasify"
    ]
  },
  "aliasify": {
    "aliases": {
      "app": "./src/app",
      "components": "./src/app/components"
    }
  },
  "devDependencies": {
    "@types/jest": "23.1.1",
    "@types/node": "^10.12.26",
    "@types/angular-mocks": "1.6.1",
    "@types/angular-resource": "1.5.14",
    "aliasify": "1.9.0",
    "angular": "1.5.11",
    "angular-animate": "1.5.11",
    "angular-bootstrap-datetimepicker": "0.4.0",
    "angular-dragdrop": "1.0.13",
    "angular-file-upload": "2.2.0",
    "angular-formly": "8.4.1",
    "angular-formly-templates-bootstrap-edgemetric": "git+https://github.com/manusinghal19/angular-formly-templates-bootstrap.git",
    "angular-jsdoc": "1.4.1",
    "angular-mocks": "1.4.8",
    "angular-moment": "0.10.3",
    "angular-numeraljs": "1.3.2",
    "angular-resource": "1.5.11",
    "angular-sanitize": "1.5.7",
    "angular-templatecache-loader": "0.2.0",
    "angular-ui-bootstrap": "2.4.0",
    "angular-ui-notification": "0.3.6",
    "angular-ui-sortable": "0.14.0",
    "angular-vs-repeat": "1.1.7",
    "angulartics": "1.0.3",
    "angulartics-mixpanel": "0.1.2",
    "api-check": "7.5.5",
    "autoprefixer": "6.5.3",
    "circular-dependency-plugin": "4.2.1",
    "clean-webpack-plugin": "0.1.19",
    "codelyzer": "4.2.1",
    "codemirror": "5.14.2",
    "compression-webpack-plugin": "1.1.11",
    "copy-webpack-plugin": "4.1.1",
    "core-js": "2.4.1",
    "coveralls": "3.0.2",
    "css-loader": "0.28.1",
    "d3-time-format": "2.1.0",
    "dateformat": "1.0.12",
    "del": "1.2.1",
    "draggabilly": "2.1.1",
    "eslint-plugin-jest": "21.17.0",
    "exports-loader": "0.6.3",
    "express": "4.13.4",
    "extract-text-webpack-plugin": "3.0.2",
    "file-loader": "1.1.5",
    "flatpickr": "3.0.7",
    "fork-ts-checker-webpack-plugin": "0.4.1",
    "formstream": "1.1.0",
    "git-rev": "0.2.1",
    "html-loader": "0.5.5",
    "html-webpack-plugin": "2.29.0",
    "http-proxy-middleware": "0.9.1",
    "humanize": "0.0.9",
    "imagemin-pngcrush": "4.1.0",
    "istanbul-instrumenter-loader": "2.0.0",
    "jest": "23.1.0",
    "jquery": "2.2.1",
    "jquery-bridget": "2.0.0",
    "jquery-ui": "1.10.5",
    "jquery.event.drag": "2.2.2",
    "jquery.scrollbar": "0.2.10",
    "jquery.scrollintoview": "git+https://github.com/EdgeMetric/jquery-scrollintoview.git",
    "jsdoc": "3.4.0",
    "jshint-stylish": "2.1.0",
    "less": "3.0.1",
    "less-loader": "4.1.0",
    "moment": "2.19.3",
    "morgan": "1.9.1",
    "packery": "2.1.1",
    "pretty-hrtime": "1.0.2",
    "qtip2": "3.0.2",
    "raw-loader": "0.5.1",
    "request": "2.83.0",
    "rrule-alt": "2.2.3",
    "run-sequence": "1.1.5",
    "rxjs": "5.5.2",
    "sass-loader": "6.0.3",
    "source-map-loader": "0.2.0",
    "style-loader": "0.21.0",
    "stylus-loader": "3.0.1",
    "tiny-lr": "0.1.6",
    "ts-jest": "22.4.6",
    "ts-loader": "3.5.0",
    "ts-node": "3.2.0",
    "tsify": "3.0.1",
    "typescript": "3.9.7",
    "uglifyify": "3.0.1",
    "uglifyjs-webpack-plugin": "1.1.8",
    "url-loader": "0.6.2",
    "vinyl-buffer": "1.0.0",
    "vinyl-source-stream": "1.1.0",
    "watchify": "3.9.0",
    "webpack": "3.10.0",
    "webpack-bundle-analyzer": "2.11.1",
    "webpack-concat-plugin": "1.4.2",
    "webpack-dev-server": "2.9.3",
    "write-file-webpack-plugin": "4.2.0"
  },
  "dependencies": {
    "@amcharts/amcharts3-angular": "2.0.3",
    "@sentry/webpack-plugin": "1.5.2",
    "@types/amcharts": "3.21.2",
    "@types/angular": "1.6.43",
    "@types/flatpickr": "3.1.2",
    "@types/jquery": "3.3.28",
    "@types/jqueryui": "1.12.13",
    "@types/lodash-es": "4.17.1",
    "@types/mixpanel": "2.14.0",
    "@types/packery": "1.4.31",
    "@types/slickgrid": "2.1.25",
    "@uirouter/angularjs": "1.0.13",
    "amcharts3": "3.21.13",
    "angular-cookies": "1.5.11",
    "angular-recaptcha": "4.1.0",
    "bootstrap": "4.3.1",
    "bowser": "1.9.2",
    "formatter-plus-plus": "0.5.10",
    "highcharts": "6.1.0",
    "highcharts-custom-events": "3.0.9",
    "humanize-plus": "1.8.2",
    "imports-loader": "^0.8.0",
    "intro.js": "2.9.3",
    "jest-html-reporter": "2.4.1",
    "lodash-es": "4.17.15",
    "mammoth-selectize": "0.12.8",
    "mixpanel-browser": "2.22.4",
    "ng-draggable": "1.0.0",
    "ng-fittext": "git+https://github.com/patrickmarabeas/ng-FitText.js.git",
    "node-sass": "4.12.0",
    "numeral": "2.0.6",
    "papaparse": "4.4.0",
    "raven-js": "3.24.0",
    "slickgrid-es6": "2.1.0",
    "tether": "1.4.4",
    "tippy.js": "2.5.2",
    "tslint": "4.5.1",
    "ui-select": "0.19.8"
  }
}
arcadeJHS commented 4 years ago

@strongSoda, have you got an online repo/zip so I can download and check it locally?

strongSoda commented 4 years ago

@arcadeJHS I fixed it.

So the issue was that the minifying code in webpack of vueApp was interfering with the base webpack. So I added a scope and that handled things well.

Also scripts don't need to be included in index.html rather imported in the app.module.js file

I'll move forward with your migration guide now.