Building an AngularJS and Vue.js into one app using webpack? - javascript

This may seem a bit convoluted, and potentially the answer is "don't do it that way, do it this way" so I'll start with the background of what I am doing. Basically I have a legacy AngularJS app that we want to migrate away from and towards Vue. We do not have the option to end-to-end rewrite the app, and need to do it piece by piece whilst still delivering a functional app.
The general goal is to therefore have both frameworks running simultaneously, my hope is that I can either do something with the routing or potentially split my single page app into 2 seperate pages for the different fraweworks. I have converted the angularjs from using a gulp build pipeline to using webpack as I assumed this was a logical first step to getting Vue to run, but now I have hit a road block.
My working webpack.config.js for building the angular app looks like the following:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
target: 'web',
// mode: "development",
// devtool: "source-map",
module: {
rules: [
{
test: /\.html$/,
loader: "html-loader",
},
{
test: /\.s[ac]ss$/i,
use: [
"style-loader",
"css-loader",
"sass-loader",
],
},
{
test: require.resolve("jquery"),
loader: "expose-loader",
options: {
exposes: ["$", "jQuery"],
},
}
],
},
entry: {
vendor: "./source/vendor.js",
main: "./source/index.js",
},
optimization: {
minimize: false
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'distribution'),
clean:true
},
plugins: [
new HtmlWebpackPlugin({
template: 'source/index.html',
})
]
};
this now works fine and I get something out add the end that works as it did before.
I then added a /source/vueJs directory, and copy & pasted the content hello world project that is generated by vue create hello-world. My assumption was if I could modify my webpack.config.js to build this, I could then iterate on it further to get to a point where it merged the two working apps together, but I'm already struggling to get the hello-world vue project to produce anything.
So far I basically commented out all the relevant angularJS parts, and added what I thought was correct to get the vue app to build, so now I have this:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
target: 'web',
mode: "development",
devtool: "source-map",
module: {
rules: [
// {
// test: /\.html$/,
// loader: "html-loader",
// },
// {
// test: /\.s[ac]ss$/i,
// use: [
// "style-loader",
// "css-loader",
// "sass-loader",
// ],
// },
// {
// test: require.resolve("jquery"),
// loader: "expose-loader",
// options: {
// exposes: ["$", "jQuery"],
// },
// },
{
test: /\.(png|jpe?g|gif)$/i,
use: [
{
loader: 'file-loader',
},
],
},
{
test: /\.vue$/,
loader: 'vue-loader'
},
// this will apply to both plain `.js` files
// AND `<script>` blocks in `.vue` files
{
test: /\.js$/,
loader: 'babel-loader'
},
// this will apply to both plain `.css` files
// AND `<style>` blocks in `.vue` files
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
}
],
},
entry: {
// vendor: "./source/vendor.js",
// angular: "./source/index.js",
vue: "./source/vueJs/index.js"
},
optimization: {
minimize: false
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'distribution'),
clean:true
},
plugins: [
new HtmlWebpackPlugin({
//template: 'source/index.html',
}),
new VueLoaderPlugin()
],
};
this runs fine and I get a distribution/ directory with my assets in, but the served site runs as if there is no javascript running at all. Comparing the output of npx webpack on my version and the output of npx vue-cli-service build on the hello-world project, the javascript is similar but different in a lot of key areas, for a start it seems as though my version does not have any HTML embedded in it like the hello world project does.
Is it a lost cause trying to compile a vue app from webpack? Can you only do it using vue-cli-service build and therefore limited to vue only? I tried modifying my vue.config.js using the info found at https://cli.vuejs.org/guide/webpack.html & https://cli.vuejs.org/config/, but frankly I feel I am out of my depth at this point and unsure if what I am doing is even a good idea.
Is there a better strategy to take to get to my end goal? If this is a workable solution, what do I need to change about my configs to get both the angularjs and the vue app to build properly?

I wasn't seeing the wood for the trees. The problem isn't that that webpack.config.js doesn't succesfully producing a working angular & vue combined app, the problem is that I'm not providing a template that actually uses either of these things, so it just produces a blank index.html with the scripts provided but no content in the body.
Changing the
new HtmlWebpackPlugin({
//template: 'source/index.html',
}),
in my question to have a template that uses both an AnularJS component and a Vue component worked fine.

Related

How to use a function in one file that was included globally with webpack

I have a static Javascript project (no react, vue, etc.) where I am trying to transpile, bundle, and minify my js with webpack. I would like to have bundle.js on my layout page which will include a bunch of global js that runs on all pages and then a page_x.js file that will be on individual pages as needed. The bundle.js file might consist of several other files and should be transpiled to es5 and minified.
With my current setup, the files are running twice. I'm not sure how to fix this. I want the file included globally but also want to be able to call the function as needed. If I delete the import statement from page.js I get the console error, "doSomething" is undefined. If I only include page.js on page.html and not on _layout.html common.js is only logged out on page.html. I want "common" to be logged once on every page and I want doSomething() to be available only on page.js.
Here is an example of it running twice:
common.js
console.log("common");
export function doSomething() {
console.log("do something");
}
page.js
import {doSomething} from "/common.js";
$(button).click(doSomething);
The expected output on page load (before clicking anything) would be:
"common"
Instead I'm seeing
"common"
"common"
My webpack.config.js file is as follows:
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const RemoveEmptyScriptsPlugin = require("webpack-remove-empty-scripts");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const WebpackWatchedGlobEntries = require("webpack-watched-glob-entries-plugin");
const CssnanoPlugin = require("cssnano");
const TerserPlugin = require("terser-webpack-plugin");
const dirName = "wwwroot/dist";
module.exports = (env, argv) => {
return {
mode: argv.mode === "production" ? "production" : "development",
entry: WebpackWatchedGlobEntries.getEntries(
[
path.resolve(__dirname, "src/scripts/**/*.js"),
path.resolve(__dirname, "src/scss/maincss.scss")
]),
output: {
filename: "[name].js",
path: path.resolve(__dirname, dirName)
},
devtool: "source-map",
module: {
rules: [
{
test: /\.s[c|a]ss$/,
use:
[
MiniCssExtractPlugin.loader,
"css-loader?sourceMap",
{
loader: "postcss-loader?sourceMap",
options: {
postcssOptions: {
plugins: [
CssnanoPlugin
],
config: true
},
sourceMap: true
}
},
{ loader: "sass-loader", options: { sourceMap: true } },
]
},
{
test: /\.(svg|gif|png|eot|woff|ttf)$/,
use: [
"url-loader",
],
},
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: "babel-loader",
options: {
presets: ["#babel/preset-env"]
}
}
}
]
},
plugins: [
new WebpackWatchedGlobEntries(),
new CleanWebpackPlugin(),
new RemoveEmptyScriptsPlugin(),
new MiniCssExtractPlugin({
filename: "[name].css"
})
],
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
extractComments: false,
})
]
}
};
};
Any help would be greatly appreciated.
Webpack is about building a dependency graph of your application files and finally producing one single bundle.
With your configuration, you are actually trying to use Webpack as a Multi-entry object configuration as explained in Webpack documents. The culprit here is WebpackWatchedGlobEntries plugin. For each file matched by a glob pattern, it would create a bundle which is not what you want ever. For exmaple, if you have following structure:
- src/scripts
- common.js
- some
- page1.js
- other
- page2.js
This plugin will produce multi-page application. So, you configuration:
entry: WebpackWatchedGlobEntries.getEntries(
[
path.resolve(__dirname, "src/scripts/**/*.js"),
path.resolve(__dirname, "src/scss/maincss.scss")
]),
will internally return an object as:
entry: {
"common": "src/scripts/common.js",
"some/page1": "src/scripts/some/page1.js",
"other/page2": "src/scripts/other/page2.js"
}
It means if you import common.js into page1.js and page2.js, then you are in producing three bundles and all those bundles will possess the common module which would be executed three times.
The solution really depends on how to you want to configure your bundle:
If you need to bundle as a multi-page application, then you must use splitChunk optimization that allows you to create page specific bundle while keeping shared code separate (common.js for example). Keep in mind that you do not really need to manually create a separate bundle for common.js. with split chunks, Webpack should do that automatically for you.
If you need a single bundle, you can literally go ahead and create a single bundle for entire application (most typical workflow with Webpack) and use the same bundle on each page. You can have a common function like run that can figure the code to call using URL or some unique page specific identifier. In modern SPA, that is done using routing module.
What I will suggest is to keep things simple. Do not use WebpackWatchedGlobEntries plugin. That will complicate things if you are not familiar with Webpack. Keep entry simple like this:
entry: {
// Note that you don't need common module here. It would be picked up as part of your page1 and page2 dependency graph
"page1": "src/scripts/some/page1.js",
"page2": "src/scripts/other/page2.js"
}
Then, enable the splitchunk optimization as:
optimization: {
splitChunks: {
chunks: 'all'
}
}
Again, there are multiple options to choose from. You can read more details here about preventing code duplication.

How to handle resources like images in your own node package

I am developing a webapp with NodeJS and webpack. It uses also ReactJS as a dependency. So let's call it simple ui webapp.
In my package.json I reference a package as a dependency which I want to use in the app. This package is not from npm, but it is developed by myself and resides on the same filesystem, local, which totally works fine. I am developing this package, lets call it ui-elements and the webapp in parallel, because I know I have to use the ui-elements in 10 about following webapp-style projects.
Back to the problem: The package gets imported when I npm install, so I have my local package inside the node_modules dir. Good.
This ui-elements-package is also bundled with webpack and contains some React components that use images as background images. Now, when I run ./node_modules/.bin/webpack inside the package folder (while developing the ui-elements package) the file-loader emits the resources into the res/ folder, like I want it to be in the webpack.config.js from the ui-elements package.
But since I want to use the ui-elements package inside my webapp, the resources reside deep inside the node_modules/ dir (node_modules/ui-elements/res).
My question:
How should my webpack.config.js in the webapp project be altered, to get the image resolving by the browser working?
And, am I thinking too complicated? I just want to build and use a package (containing some ui elements with background images), that i can reuse in React webapps. Is there a better approach?
I will paste the extracts of the webpack configs of both projects:
ui-elements
webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'index.js',
libraryTarget: 'commonjs2',
path: __dirname
},
module: {
rules: [
{
test: /\.jsx?$/,
include: path.resolve(__dirname, 'src'),
exclude: path.resolve(__dirname, 'node_modules'),
use: [
'babel-loader'
]
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.(jpe?g|png|gif|svg)$/i,
use: [
{
loader: 'file-loader',
options: {
name: 'res/img/[name].[ext]'
}
}
]
}
]
},
resolve: {
extensions: ['.js', '.jsx'],
modules: [
path.resolve(__dirname, 'src'),
path.resolve(__dirname, 'node_modules')
],
}
};
webapp
webpack.config.js
const path = require('path');
module.exports = {
entry: './index.js',
output: {
filename: 'webapp.bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: path.resolve(__dirname, 'node_modules'),
use: [
'babel-loader'
]
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
]
},
resolve: {
/*
* this entry makes me use a local working tree of the
* ui-elements package, which makes working easier, i don't
* have to switch projects, rebuild the package and update my
* dependencies in this webapp project
*/
/*
alias: {
"ui-elements": path.resolve(__dirname, 'libDev'),
},
*/
extensions: ['.js', '.jsx'],
modules: [
path.resolve(__dirname, 'src'),
// path.resolve(__dirname, 'libDev'),
path.resolve(__dirname, 'node_modules')
],
}
};

Webpack bundled JS not being executed

This is a very strange problem because actually, some of the bundled code is being executed. I use style loader for my CSS and that of course gets put into bundle.js and loads and works fine. However, I also have a file with some code to set up the jQuery localScroll plugin, and that code isn't working.
To test it, I included in the same file a call to console.log(), just telling it to write the number 4. If I open up bundle.js, I can see the console.log() call as well as the call to $.localScroll(), they just simply aren't running. Calling $.localScroll() manually from the console works as intended.
Here is the JS file in question:
console.log(4);
$(() => {
$.localScroll({duration: 800});
});
Here is my Webpack config:
const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
entry: './webpack-entry.js',
plugins: [
new CleanWebpackPlugin(['dist']),
new webpack.optimize.UglifyJsPlugin()
],
output: {
filename: './javascripts/bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.scss$/,
use: [
'style-loader',
'css-loader',
'sass-loader'
]
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.(png|svg|jpg|jpeg)$/,
use: {
loader: 'url-loader',
options: {
limit: 8192,
fallback: 'file-loader',
name: './images/[hash].[ext]'
}
}
},
{
test: /\.pug$/,
use: [
{
loader: 'file-loader',
options: {
name(file) {
if(new RegExp(/partials/).test(file)) {
return './views/partials/[name].[ext]'
}
return './views/[name].[ext]'
}
}
},
'pug-asset-loader?root=./src'
],
},
{
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['env'],
}
},
]
}
],
}
};
Finally, here is bundle.js (my custom code seems to be at the very bottom, in some sort of array of functions). The non-uglified version is too long for SO, so here it is on Hastebin: https://hastebin.com/vululimupi.js
The problem is that you haven't defined this dependency as a module. Rewriting it following supported module format specs should help.
After some testing, it seems that the essential problem is just as #uKolka was saying - my files were not getting required as modules. While I'm still not entirely certain why their code was still appearing in my bundle file but not running, I have found a way to still reap the recursive benefits of require.context(). It seems that require.context() returns a function which is itself capable of resolving the files it logs in whatever folder you have pointed it at. It also has a member function keys() which quite conveniently returns each dependent file name and is easily used with forEach().
Given all that, this is how my webpack-entry.js looks now:
import './src/stylesheets/scss/master.scss';
require.context('./src/views', true, /\.pug$/);
const js = require.context('./src/javascripts/', false, /\.js$/);
js.keys().forEach(key => js(key));
This works just fine.
In my case it was due to Babel and React. If you use React, then try to call ./src/index.js directly and use "#babel/preset-env", "#babel/preset-react" in your .babelrc. You can take a look at my gist. and in your webpack.config.js:
{
test: /\.js?$/,
use: ["babel-loader"],
exclude: /node_modules/
},
take a look at the gist:
https://gist.github.com/Nagibaba/14e898d99a4be89b00a60d28abc19bc0
For ruby's rails/webpacker users only.
This question title and content made me find it before this issue which may be the correct answers for some users here. To avoid link rot, I'll explain it shortly below:
Make sure your #rails/webpacker npm library and webpacker gem both have the same exact version.
I ended up doing:
IO.foreach("package.json").find { |line| line[%r("#rails/webpacker": "(.*?)")]}
gem "webpacker", Regexp.last_match(1).tr("-", ".")

react-dom blowing out webpack bundle size MASSIVELY

This has got to be one of the strangest issues with webpack i have ever come across...
Check out this bundle breakdown:
react 116.01KB - fair enough
react-dom 533.24KB - seriously WTF
I thought it may be a corruption in my dependencies but nuking node_modules and reinstalling doesn't have any effect. I guess it's something to do with the way webpack is bundling it but i'm lost for ideas. The way i'm handing .js imports is pretty stock standard.
// webpack.config.js
const path = require('path');
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const Dashboard = require('webpack-dashboard');
const DashboardPlugin = require('webpack-dashboard/plugin');
const dashboard = new Dashboard();
module.exports = {
context: path.join(__dirname, 'src'),
entry: {
bundle: './index.js',
},
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'build'),
},
module: {
rules: [
{
test: /\.html$/,
use: 'file-loader?name=[name].[ext]',
},
{
test: /.scss$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
'css-loader',
'postcss-loader',
],
}),
},
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader',
},
],
},
plugins: [
// new BundleAnalyzerPlugin(),
new ExtractTextPlugin('styles.css'),
new DashboardPlugin(dashboard.setData),
],
devServer: {
quiet: true,
},
};
// .babelrc
{
"presets": [
"react",
"es2015"
],
"plugins": ["transform-object-rest-spread"]
}
http://elijahmanor.com/react-file-size/
In v15.4.0 the file size of react-dom grew from 1.17kB to 619.05kB. Which means my webpack setup isn't doing anything wrong bundling files. The reason why this module grew so large is because code was transferred from the react module.
I had to change my webpack.config.js, from
devtool: 'inline-source-map'
to
devtool: 'source-map'
Now it generates a much smaller .js + a separate .js.map file, for each of the chunks.
Notice the JS size is even less than react-dom.production.min.js in node_modules:
If you look into the corresponding folders under the node_modules folder, and note the file sizes, you'll see that there's nothing to be surprised about:
That is, the size of the bundle grows noticeably because the size of react-dom.js is large.
Add this following commands at plugins to minify your imports:
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.DefinePlugin(GLOBALS),
new webpack.optimize.UglifyJsPlugin(),
You should create a file or option to production bundle to use this plugins

Compiling Sass with Webpack (and local scope class names)

I've spent hours attempting to get my Webpack config to compile Sass; it's kinda ridiculous. During my research I found dozens of Github issues, Stackoverflow posts, and blogs talking about how to use Sass with Webpack, and they all do it differently. Also, there are so many people with problems. I just think Webpack needs to be better documented. Ugh.
I figured out how to compile Sass and have Webpack serve it in memory from /static, but I want the class names to be locally scoped. Isn't that one of the benefits of modular CSS with React components?
Example of locally scoped: .foo__container___uZbLx {...}
So, this is my Webpack config file:
const webpack = require('webpack');
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
devtool: 'source-map',
entry: {
bundle: './src/js/app'
},
output: {
path: __dirname,
filename: '[name].js',
publicPath: '/static'
},
plugins: [
new webpack.optimize.OccurrenceOrderPlugin(),
new ExtractTextPlugin('[name].css', {allChunks: true})
],
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
include: path.join(__dirname, 'src'),
loader: 'babel'
},
{
test: /\.scss$/,
exclude: /node_modules/,
include: path.join(__dirname, 'src'),
loader: ExtractTextPlugin.extract('style', 'css?sourceMap!sass')
}
]
}
};
I managed to get it to work for vanilla CSS:
{
test: /\.css$/,
exclude: /node_modules/,
include: path.join(__dirname, 'src'),
loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]')
}
I don't really understand the parameter-like syntax with all the ? marks, and I don't know what to search for to find documentation pertaining to that.
This is what my React component looks like; just incase you want to see how I am importing the style:
import React, { Component } from 'react';
import s from './foo.css';
class Foo extends Component {
render() {
return (
<div className={s.container}>
<h1 className="title">Welcome!</h1>
<p className="body">This is a dummy component for demonstration purposes.</p>
</div>
);
}
}
export default Foo;
Also, I have three unrelated questions:
What's the point of output.path property if Webpack merely serves the file from memory by means of /static?
What's the point of webpack-dev-server if what I am doing here is adequate? From my understanding, webpack-dev-server is just for hot module replacement stuff, right? Just automatic refreshing?
Are my exclude and include properties redundant? From my understanding, excluding node_modules decreases the compilation time making it work quicker; less files to process.
I got it to work with this:
loader: ExtractTextPlugin.extract('style', 'css?modules&localIdentName=[name]__[local]___[hash:base64:5]!sass')
All I had to do was put !sass at the end of the query. I wish this stuff was better documented; can't find adequate docs anywhere...

Categories