How to migrate from imports using <script> to module bundler? - angularjs

I have a project which uses Angular. It does not use a task manager nor dependency manager and all libs are stored in repo and included using plain old <script> tag like <script src="libs/angular/angular.min.js"></script>.
I want to modernize the application by introducing a module bundler and automating the build process. The original idea was gulp + bower, but I see that webpack + npm3 are a trend now.
There is no issue with gulp + bower because of things like gulp-inject, but I can’t find anything similar which works with webpack.
Are there any tools which would allow me to use the existing code as it is and write only new modules using webpack?

Here's the solution i've got so far. Since the webpack requires a single entry point per bundle, i still have to import all those legacy js in a single file. So i decided to use ES6 import (or webpack's require) in that entrypoint called index.js
here's the how webpack config looks like
var webpack = require('webpack');
var path = require('path');
var basePath = __dirname + "/target/app";
module.exports = {
context: basePath,
entry: {
app: './index.js',
vendor: './vendor.js'
},
output: {
path: basePath + "/build",
filename: '[name].js'
},
module: {
loaders: [
{test: /\.js$/, loader: 'babel?presets=es2015', exclude: /node_modules/},
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor'
}),
new webpack.ProvidePlugin({
"$": "jquery",
"jQuery": "jquery",
"window.jQuery": "jquery",
"_": "lodash"
}),
],
};
and so in index.js i did following
import './app/component1.js'
import './app/component2.js'
//all the rest imports from index.html
as for the third part libraries like jquery, angular, etc... i decided to describe that imports in separate bundle and so there's another bundle called vendor.js
import './libs/lodash.js'
import './libs/jquery.js'
//and all the rest libraries
//note that here we can use now libs from npm node_modules like
import 'angular' //will take angular from node_modules/angular
Important thing which i faced while doing this, is that legacy modules are not really compatible with webpack because of using global variables like $, _, etc... ProvidePlugin helps here and add require({module}) to the modules where the provided {variable} met as it's described in config 'variable': 'module'
Note that i used CommonsChunkPlugin to avoid having dependent modules in both bundlers, like angular is required in index.js and vendor.js but with this plugin webpack will handle that and add js code only in vendor.js

Related

sharing react between microfrontend bundles without CDN

I am building an app using a microfrontends architecture with the Single-Spa framework.
I have:
A root config web app: Defines the layout of the apps and includes each microfrontend as an NPM dependency
N Microfrontends: React apps which are loaded into the root config app.
Because each microfrontend is using React, I don't want to bundle it everytime. Instead I want to have the root config app specify React as a dependency and then it can be loaded by each microfrontend.
I have declared react and react-dom as webpack externals so they are not bundled but I think this will only work if I include React from CDN in my index.html of the root config app. Is there a way to configure webpack so that it will include React in the root config and make it available to every microfrontend? I can't use public CDNs in my firm.
Thanks!
In the "Getting Started: Quick start" guide, we cover how to add shared dependencies, and the example specifically shows adding React and ReactDOM to the import map.
You should not try to bundle React into the root-config, since modules bundled into an application by Webpack are namespaced and not shared globally. Instead, per your requirement, you can host those React files in your own CDN or even serve them locally though the root-config because ultimately there are static files. The location of where those files are loaded from is not relevant to SystemJS as long as it can load them. This means you can go as simple as copying those files locally and serving them from the root-config server, to as complicated as setting up your own unpkg server.
Yes, we can do that not just for the React Package but with all the dependencies and share them between all the micro-frontend apps.
Webpack 5, provides the support of ModuleFederationPlugin in which you can define the list of packages. Example:-
Import Package.json in the Webpack configuration and add following plugin
const packageJson = require('../package.json');
{
plugins: [
new ModuleFederationPlugin({
name: <AppName>,
filename: 'remoteEntry.js',
exposes: {
'./App': <PathToFile>,
},
shared: {
...packageJson.dependencies,
}
}),
],
}
You can explicitly define only the packages name which you want to share.
Check out more about ModuleFederationPlugin
If you want to have flexibility in micro frontend, that is, if you want each micro frontend to be deployed independently of each other, you can use client-side intgration.
React
You can use a CDN for static files. (image, fonts, pdf, doc) Webpack module federation can be used for client-side integration.
Configuring an example webpack and module federation.
We divide the webpack config files into 3 parts. for common, development, production
webpack.common.js
module.exports = {
module: {
rules: [
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-react', '#babel/preset-env'],
plugins: ['#babel/plugin-transform-runtime'],
},
},
},
],
},
};
webpack.dev.js
const { merge } = require('webpack-merge');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const commonConfig = require('./webpack.common');
const packageJson = require('../package.json');
const devConfig = {
mode: 'development',
output: {
publicPath: 'http://localhost:8082/',
},
devServer: {
port: 8082,
historyApiFallback: {
index: 'index.html',
},
},
plugins: [
new ModuleFederationPlugin({
name: 'auth',
filename: 'remoteEntry.js',
exposes: {
'./AuthApp': './src/bootstrap',
},
shared: packageJson.dependencies,
}),
new HtmlWebpackPlugin({
template: './public/index.html',
}),
],
};
module.exports = merge(commonConfig, devConfig);

Webpack+SemanticUI+React: process is not defined

I have found numerous posts about the Webpack error:
Uncaught ReferenceError: process is not defined
most of which suggest adding a plugin to the webpack.config.js:
plugins: [
// ...
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'development')
}
}),
// ...
]
however this does not seem to do the trick in my case.
To make things easy, I have created a repo with the bare minimum to setup SemanticUI-React with Webpack, which should be straightforward to navigate. My config in webpack.config.js is heavily inspired from this recent tutorial which seems to have a lot of positive comments.
To reproduce the error, just clone the repo on your machine (I use yarn, but this should work with npm too):
git clone https://github.com/sheljohn/minimal-semantic-react
cd minimal-semantic-react/
yarn install
yarn run serve
which opens at localhost:3000, and the error can be seen in the developer console.
As far as I understand, it seems that when React loads, it is looking to determine whether production or development mode is set, using the variable process.env.NODE_ENV, which is undefined in the browser.
This might be related to the target field in the Webpack config (set to web by default); but since React is loaded from CDN, prior to the bundle, I guess it doesn't know about what WebPack is doing, which makes me perplex as to why adding a plugin in the config would change anything...
Hence my question: is it possible to use semantic-ui-react by declaring the big libs (React, ReactDOM, semantic) as externals? Everything works fine if I bundle them, but the bundle ends up around 4MB, which is quite big.
Additional Details
Error as seen in Chrome (OSX High Sierra, v66.0.3359.181, dev console):
react.development.js:14 Uncaught ReferenceError: process is not defined
at react.development.js:14
(anonymous) # react.development.js:14
and code excerpt at line 14:
if (process.env.NODE_ENV !== "production") {
File webpack.config.js
const path = require("path");
const webpack = require("webpack");
const publicFolder = path.resolve(__dirname, "public");
module.exports = {
entry: "./src/index.jsx",
target: "web",
output: {
path: publicFolder,
filename: "bundle.js"
},
devServer: {
contentBase: publicFolder,
port: 3000
},
externals: {
'jquery': 'jQuery',
'lodash': '_',
'react': 'React',
'react-dom': 'ReactDOM',
'semantic-ui-react': 'semantic-ui-react'
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader'
}
}
]
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'development')
}
}),
new webpack.HotModuleReplacementPlugin()
]
};
File .babelrc
{
"presets": ["env", "react"]
}
I think I finally solved this:
Mistake #1: I was using cjs versions of the React libs from cdnjs, when I should have been using umd instead. Although UMD style is ugly, it seems to work fine within browsers, whereas CommonJS uses require for example. See this post for a comparison of AMD / CommonJS / UMD.
Mistake #2: in webpack.config.js, the "name" for the external semantic-ui-react should be semanticUIReact (case sensitive). This is what is defined in the window global when the script is loaded from the CDN (e.g. like jQuery or React).
I updated the repository with these fixes, and you should be able to reproduce that working example on your machine. This repository contains the bare minimum needed to get SemanticUI, React and Webpack working together. This would have saved me a lot of time, so hopefully other people get to benefit from that!
Everything works fine if I bundle them, but the bundle ends up around 4MB, which is quite big.
It's because you bundle them in "development" mode. Try using "production" in your script instead, it will be much smaller.
"build": "webpack --mode production"
If you bundle everything in production, without specifying external, it will be better for a standalone app.

How to build unified $templateCache when migrating AngularJS 1.x project from gulp & bower to webpack 3

Have a large AngularJS 1.6 project (~120 JS files), which is currently built through gulp/bower.
Looking to migrate over to using yarn & webpack, and we would prefer not to have to modify our project files just to implement webpack. Each of our AngularJS component JS files are wrapped in a IIFE, so they are already out of the global scope.
One of our gulp steps uses the gulp-angular-templatecache module, which collects all of our HTML files and neatly compacts them into a single $tmplateCache loader module.
angular.module("ourApp").run(["$templateCache", function ($templateCache) {
$templateCache.put("/app/components/ourComponent1.html", "<div id=\"component1-html\"> ... </div>\r\n");
$templateCache.put("/app/components/ourComponent2.html", "<div id=\"component2-html\"> ... </div>\r\n");
// etc...
});
I have managed to figure out how to resolve most of our other build processes using webpack, but this one is giving me a problem.
I have looked at the ngtemplate-loader package, but do not think this would work for our needs because of the following:
Would require us to update all of our existing HTML templates to use 'require("./template.html")'
This would create a separate webpack module for each HTML template, which seems very inefficient.
Probably most important, I haven't been able to get it to work.
The current webpack configuration I have setup is based on a simple demo, and splits the project files out from the vendor files. Our project files are bundled up into a 'app.[hashcode].js' file into the /dist folder.
Ultimately, I would like to be able to inject the compiled $templateCache module, into a specific point in our final 'app.[hashcode].js' bundle file. However, right now I would be satisfied with the $templateCache definition file being created into a separate bundle file.
Is there an existing webpack plugin, or loader, or combination of plugin(s)/loader(s), that I could use to accomplish this build step? Is this even possible with webpack?
This is the base directory structure for the demo project:
/dashboard
/app
/assets
/global
global.less
app.less
/components
/controllers
dashboard.controller.js
/directives
yep-nope.directive.js
/messenger
messenger.directive.js
messenger.html
/services
github-status.service.js
dashbboard.config.js
dashboard.module.js
app.js
/dist
app.4f12bb49f144e559bd9b.js
assets.css
index.html
vendor.b0a30c79aa77e0126a5c.js
index.html
package.json
webpack.config.js
This is the current working webpack.config.js file:
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
context: __dirname,
entry: {
app: path.resolve(__dirname, 'app/app.js'),
vendor: ['angular','angular-sanitize']
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[chunkhash].js'
},
module: {
rules: [
{
test: /\.less$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: ['css-loader', 'less-loader']
})
},
{
test: /\.js$/,
loader: 'ng-annotate-loader'
},
{
test: /\.html$/,
exclude: path.resolve(__dirname, 'app/index.html'),
use: [
{ loader: 'html-loader', options: { minimize: false } }
]
}]
},
plugins: [
new CleanWebpackPlugin(['dist','assets/**/*.css']),
new HtmlWebpackPlugin({
title: 'GitUp',
template: 'index.html',
inject: 'body'
}),
new webpack.optimize.CommonsChunkPlugin({
name:"vendor", filename:"[name].[chunkhash].js"
}),
new ExtractTextPlugin('assets.css')
]
};
This is the webpack 'entry' file app/app.js:
require('./assets/app.less');
require('../node_modules/angular/angular.js');
require('../node_modules/angular-sanitize/angular-sanitize.js');
require('./components/dashboard.module.js'); // Define module
require('./components/dashboard.config.js'); // Setup config
// Load in all components files, except for files already loaded above
var reqCtx = require.context('./components', true, /^(?!.*(?:dashboard\.config|dashboard\.module)).*\.js$/);
reqCtx.keys().forEach(reqCtx);
If needed, I can provide the entire sample project...
This may or may not be the answer you're looking for but in the past I've used html-loader to allow me to require my component template URLs, and it worked brilliantly:
https://www.npmjs.com/package/html-loader
It just inlines the template in your bundled script.

angular 2 web worker Uncaught SyntaxError: Unexpected token import [duplicate]

I'm trying to move the whole execution of an Angular 2 app to a web worker, but I've find out that all examples available right now are using System.js, and I'm trying to do so with a WebPack based project (built with angular-cli).
Has anyone done this or have an idea on how to do this with WebPack?
Below is my main.ts bootstrap file:
import './polyfills.ts';
import { enableProdMode } from '#angular/core';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
import {bootstrapWorkerUi} from '#angular/platform-webworker';
bootstrapWorkerUi('/app/loader.js');
My loader.js looks like this:
import {platformWorkerAppDynamic} from '#angular/platform-webworker-dynamic';
import { AppModule } from './app/';
platformWorkerAppDynamic().bootstrapModule(AppModule);
And my app.module.tsis declaring #NgModulethis way:
import { NgModule } from '#angular/core';
import {WorkerAppModule} from '#angular/platform-webworker';
import { AppComponent } from './app.component';
#NgModule({
declarations: [
AppComponent
],
imports: [
WorkerAppModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
However, when I run it, I get the error Uncaught SyntaxError: Unexpected token import in loader.js:1.
The issue seems related with webpack and some lack of imports of #angular libs in the webworker side, as suggested by Tamas Gegedus and MathMate, so the next step is to modify the webpack config file in order to provide all the required libs to the webworker. I'll work on that.
-----------
UPDATE
-----------
Since angular-cli do not provide access to webpack.config.js file, I've included mine, making little changes in code so it can work. I created the second entry point at webpack config file and I'm getting the error VM33:106 Uncaught ReferenceError: window is not defined.
Webpack config file looks like this:
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var helpers = require('./config/helpers');
module.exports = {
entry: {
'app': ['./src/polyfills.ts', './src/vendor.ts', './src/main.ts'],
'webworker': ['./src/workerLoader.ts']
},
resolve: {
extensions: ['', '.ts', '.js']
},
output: {
path: helpers.root('dist'),
publicPath: 'http://localhost:8080/',
filename: '[name].js'
},
devtool: 'cheap-module-eval-source-map',
module: {
loaders: [
{
test: /\.ts$/,
loaders: ['awesome-typescript-loader?tsconfig=./src/tsconfig.json', 'angular2-template-loader']
},
{
test: /\.html$/,
loader: 'html'
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
loader: 'file?name=assets/[name].[hash].[ext]'
},
{
test: /\.css$/,
exclude: helpers.root('src', 'app'),
loader: ExtractTextPlugin.extract('style', 'css?sourceMap')
},
{
test: /\.css$/,
include: helpers.root('src', 'app'),
loader: 'raw'
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html',
excludeChunks: ['webworker']
}),
new ExtractTextPlugin('[name].css')
]
};
where polyfills.ts and vendor.ts include polyfills and angular libs respectively.
I've created 2 entry points, that generate 2 outputs (app.js and webworker.js).
With the HtmlWebpackPlugin, the first one is included in the index.html file, but not the second one.
When index.html is included, main.ts (contained in app.js) is called and tries to bootstrap the webworker using the second output of webpack (webworker.js):
import {bootstrapWorkerUi} from '#angular/platform-webworker';
bootstrapWorkerUi('../webworker.js');
webworker.js has been generated by webpack using workerLoader.ts entry, that is like previous loader.js, but including some imports.
WorkerLoader.ts file looks like this:
import './polyfills.ts';
import '#angular/core';
import '#angular/common';
import {platformWorkerAppDynamic} from '#angular/platform-webworker-dynamic';
import { AppModule } from './app/';
platformWorkerAppDynamic().bootstrapModule(AppModule);
I know that the web worker is been generated, as shown in the following image, but as shown in the image, I get the error VM33:106 Uncaught ReferenceError: window is not defined, and nothing is displayed except the Loading message from index.html.
On the other side, I think that the webpack config is OK because if running the app with webpack in NO webworker mode, it works fine.
Right now I think that this issue is related with webworker bootstrap, but I'm quite stucked here.
Any help would be appreciated ;)
I finally solved the issue :)
NOTE: if you have generated the project using Angular CLI 1.0 or higher, you can now use their own webpack config file, and that simplifies the whole process. Check this answer in that case.
After running the code with my custom webpack config, I realized that the error VM33:106 Uncaught ReferenceError: window is not defined was caused by webpack-dev-server.
I solved it by:
1 - doing a standalone webpack build
$: webpack --watch #this creates the bundes at myProject/dist
and
2 - running with another devserver tool like simplehttpserver
$: npm install simplehttpserver -g
$: simplehttpserver -p 8080 dist/ #as my webpack bundle is included in index.html using that port by default
And voilà, the error is gone and it simply works!
 
Changes applied to angular-cli project: Webpack config
Let me summarize the webpack changes regarding the original Angular-CLI project.
As angular-cli do not provide access to webpack.config.js file and customizing webpack is key to use webworkers, I've included mine, making little changes in code.
Webpack config file looks like this:
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var helpers = require('./config/helpers');
module.exports = {
entry: {
'app': ['./src/polyfills.ts', './src/vendor.ts', './src/main.ts'],
'webworker': ['./src/workerLoader.ts']
},
resolve: {
extensions: ['', '.ts', '.js']
},
output: {
path: helpers.root('dist'),
publicPath: 'http://localhost:8080/',
filename: '[name].js'
},
devtool: 'cheap-module-eval-source-map',
module: {
loaders: [
{
test: /\.ts$/,
loaders: ['awesome-typescript-loader?tsconfig=./src/tsconfig.json', 'angular2-template-loader']
},
{
test: /\.html$/,
loader: 'html'
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
loader: 'file?name=assets/[name].[hash].[ext]'
},
{
test: /\.css$/,
exclude: helpers.root('src', 'app'),
loader: ExtractTextPlugin.extract('style', 'css?sourceMap')
},
{
test: /\.css$/,
include: helpers.root('src', 'app'),
loader: 'raw'
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html',
excludeChunks: ['webworker']
}),
new ExtractTextPlugin('[name].css')
]
};
where polyfills.ts and vendor.ts include polyfills and angular libs respectively.
I've created 2 entry points, that generate 2 outputs (app.js and webworker.js).
With the HtmlWebpackPlugin, the first one is included in the index.html file, but not the second one.
When index.html is included, main.ts (contained in app.js) is called and bootstraps the webworker like this:
import {bootstrapWorkerUi} from '#angular/platform-webworker';
bootstrapWorkerUi('../webworker.js');
webworker.js is the code webpack generated using workerLoader.ts entry.
WorkerLoader.ts file looks like this:
import './polyfills.ts';
import '#angular/core';
import '#angular/common';
import {platformWorkerAppDynamic} from '#angular/platform-webworker-dynamic';
import { AppModule } from './app/';
platformWorkerAppDynamic().bootstrapModule(AppModule);
And AppModule looks like this:
import { NgModule } from '#angular/core';
import {WorkerAppModule} from '#angular/platform-webworker';
import { AppComponent } from './app.component';
#NgModule({
declarations: [
AppComponent
],
imports: [
WorkerAppModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Code example
To make easier to understand the differences, I've created a project showing how to convert a regular angular 2 project in a web workers based one. Check this repo: https://github.com/kaikcreator/webWorkerFactorialExample
You'll see in master branch the "single thread" code, and in webWorkers branch, the changes to run code using web workers.
As Tamas Hegedus has already mentioned in comment, most likely your webpack bundle is still referring es6 file for loader.js.
You need to create separate bundle with entry point for loader script.
If you share your webpack config then it would be easier to tell where the things might be going wrong.
Also your loader script needs to have required polyfills. You can add
import 'core-js/es7/reflect';
import 'zone.js/dist/zone';

how to get webpack to bundle node_modules for angular 1

I am about to move angular 1 + typescript project build setup from gulp to webpack, the only part I am stuck with is, how to bundle node_modules js file in proper sequence.
I was till now using bower for client side dependencies, and gulp has wiredep plugin which will look into bower dependencies section + main section to build the dependency order and get it to bundle it properly.
Now I understand that webpack philosophy is different, we should depend upload whatever is imported rather than any dependencies section as such.
so to get it to work I will need to do: move all dependencies from bower.json to package.json
currently as I am using typings, tsc considers the typings and gives me the output, I really don't need to write imports for bower packages as such.
So if i understand correctly, to get it work with webpack,
a) I should get away with typings and then directly import the js
files inside all my ts files??
As I understand, all the js modules from npm do work with modules, so does webpack really needs typings files?
OR
b) I should write a separate vendor.ts, where I should maintain the
sequence of imports (for js and css) myself,
but then that would be little painful to do (given I am used to wiredep handling it for me).
But then this will allow me bundle the vendor files separately using chunks-plugin
OR
c) is there any other standard way to handle this.
This is kinda pain point to move angular 1 from gulp to webpack.
When you import a module from a TypeScript file that's going to be loaded by webpack, it'll get it's content from node_modules and bundle it into the output file.
index.ts
import * as angular from "angular";
const myApp = angular.module("myApp", []);
myApp.controller("MyController", function PhoneListController($scope) {
$scope.phones = [
{
name: "Nexus S",
snippet: "Fast just got faster with Nexus S."
}, {
name: "Motorola XOOM™ with Wi-Fi",
snippet: "The Next, Next Generation tablet."
}, {
name: "MOTOROLA XOOM™",
snippet: "The Next, Next Generation tablet."
}
];
});
index.html
<!DOCTYPE html>
<html ng-app="myApp">
<head>
...
<script src="bundle.js"></script>
</head>
<body ng-controller="MyController">
<ul>
<li ng-repeat="phone in phones">
<span>{{phone.name}}</span>
<p>{{phone.snippet}}</p>
</li>
</ul>
</body>
</html>
webpack.config.js
module.exports = {
entry: "./index.ts",
output: {
filename: "./bundle.js"
},
devtool: "source-map",
resolve: {
extensions: ["", ".webpack.js", ".web.js", ".ts", ".tsx", ".js"]
},
module: {
loaders: [
{ test: /\.tsx?$/, loader: "ts-loader" }
],
preLoaders: [
{ test: /\.js$/, loader: "source-map-loader" }
]
}
};
Add angular, #types/angular, ts-loader, source-map-loader, typescript to your packages.json and run webpack.
It'll bundle everything inside a single file named bundle.js which will be used by your index.html.

Resources