Integrate Angular and Webpack in an ASP.NET MVC Application [closed] - asp.net-mvc

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 5 years ago.
Improve this question
I am looking for a write up with steps i can follow to add Angular to an existing MVC application that lives in an area of my solution. I have found a write up that shows me how to add angular using a gulp.js file that pulls the necessary node_modules and transpiles my ts files into js files. I would like to do the same exact thing using webpack instead.
Currently my package.json looks like this.
{
"version": "1.0.0",
"name": "aspnet",
"private": true,
"scripts": {},
"dependencies": {
"#angular/animations": "4.3.5",
"#angular/common": "4.3.5",
"#angular/compiler": "4.3.5",
"#angular/compiler-cli": "4.3.5",
"#angular/core": "4.3.5",
"#angular/forms": "4.3.5",
"#angular/http": "4.3.5",
"#angular/platform-browser": "4.3.5",
"#angular/platform-browser-dynamic": "4.3.5",
"#angular/platform-server": "4.3.5",
"#angular/router": "4.3.5",
"#angular/upgrade": "4.3.5",
"angular-in-memory-web-api": "0.3.2",
"bootstrap": "3.3.7",
"core-js": "2.5.0",
"ie-shim": "0.1.0",
"rxjs": "5.4.3",
"zone.js": "0.8.16",
"systemjs": "^0.20.18"
},
"devDependencies": {
"gulp": "^3.9.1",
"gulp-clean": "^0.3.2",
"gulp-concat": "^2.6.1",
"gulp-tsc": "~1.3.2",
"gulp-typescript": "^3.2.2",
"path": "^0.12.7",
"typescript": "^2.4.2"
}
}
This is the write up I followed that uses gulp and this works.
But i would much rather prefer using webpack instead of a gulp task.

I hope that can help you find the idea because our projects also need to integrate webpack and ASP.NET MVC together. Noted that it is my own proposal so that there might be a better way to do it. Below is what we do.
Check the source code on Github
1. Structure the project
We separated our project into two folders: Client and Server. Those will be located in mvc5-angular-webpack folder and this folder will be committed to the repository
mvc5-angular-webpack/
├── Server/
│ ├── WebApplication/
│ │ ├── Controllers/
│ │ ├── Scripts/
│ │ ├── Web.config
│ │ ├── Many more folder and file...
│ │
│ └── Web-Core.sln
│
├── Client/
├── modules
│ ├── angularModule-1/
│ │ ├── main.ts
│ │ ├── app.modules.ts
│ │ ├── app.component.ts
│ │ ├── Many more file...
│ │
│ ├── angularModule-2/
│ │ ├── main.ts
│ │ ├── app.modules.ts
│ │ ├── app.component.ts
│ │ ├── Many more file...
│ ├── polyfill.ts
│ ├── vendor.ts
│
└── build.bat
└── npm-shrinkwrap.json
└── package.json
└── tsconfig.json
└── tslint.json
└── webpack.config.js
In the Server folder, we added the MVC solution named Web-Core.sln
and all the common library project is written in C#.
In the Client folder only contains front-end related stuff. To build the front project, simply call the build.bat. I will talk about this file content later. Inside modules folder, our project will create each subfolder for each module.
Our website has some module still using server-side rendering with pure Razor. And there is some module written in client-side code with AngularJS and Angular.
2. Configure webpack
Assume that you configured all the typescript and npm already. Let see what I have inside webpack.config.js
const webpack = require('webpack')
const path = require('path')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const entryPath = path.resolve(__dirname, 'modules')
const corePath = path.resolve(__dirname, '../Server/WebApplication/Scripts/ng2')
const module1 = `${entryPath}/angularModule-1`
const module2 = `${entryPath}/angularModule-2`
module.exports = (envOptions) => {
envOptions = envOptions || {};
const config = {
entry: {
'polyfills': `${entryPath}/polyfill.ts`,
'vendors': `${entryPath}/vendor.ts`,
'module1': `${module1}/main.ts`,
'module2': `${module2}/main.ts`
},
output: {
path: corePath,
filename: '[name].js',
sourceMapFilename: "[name].js.map"
},
resolve: {
extensions: ['.ts', '.js', '.html']
},
module: {
rules: [
{
test: /\.ts$/,
loaders: ['awesome-typescript-loader', 'angular2-template-loader']
},
{
test: /\.html$/,
loader: 'raw-loader'
},
{
test: /\.css$/,
loader: 'raw-loader'
}
]
},
devtool: 'source-map',
plugins: [
new webpack.NoEmitOnErrorsPlugin(),
new webpack.optimize.CommonsChunkPlugin({
name: ['vendors', 'polyfills']
})
]
}
if (envOptions.MODE === 'prod') {
config.plugins.push(
new UglifyJsPlugin()
)
}
return config;
}
So basically it will try to resolve the directory in an upper level and put all the compiled files to Scripts/ng2 inside Server folder.
3. Configure npm script
After configured webpack, we will basically add some more script to run during the build process. Add the following code to package.json file
"scripts": {
"tsc": "tsc",
"tsc:w": "tsc -w",
"dev": "webpack-dev-server --https --open",
"watch": "webpack --config webpack.config.js --watch",
"build": "webpack --config webpack.config.js",
"build:html": "webpack --config webpack-html-plugin.config.js --env.MODE=prod",
"build:prod": "webpack --config webpack.config.js --env.MODE=prod"
}
4. Configure build.bat
At the beginning of Angular 2 integration, we have created an empty web application project for front-end purpose and added this project as a dependency of our WebApplication. But our backend team later complained about how slow the front-end process take. Because they don't need the front-end project to build every time the WebApplication is being built.
The idea of the build.bat file is to manually run it to get the latest version of front-end code on their machine. Not every single time they run the project.
call npm install --production --loglevel verbose
echo "Build Angular projects"
npm run build:prod
The call is to continue because some of the commands abort the command line. Refer here
The script here is very simple. First, we run the npm install to restore all the necessary dependency. Then call build:prod as we defined on package.json before. Webpack will take care of bundle our Typescript code into three big JavaScript files as vendors.js, polyfills.js and module1.js.
Our team used Jenkins for deployment, so our dev ops just need to include to run the build.bat and we are all set. If you want to run it everytime your project was built, you can set it inside the pre-build or post-build event.
Image source
5. Reference compiled JavaScript file in the view.
Normally we will return only one view in an area as below. The my-angular-app is what we defined in app.component.ts
Index.cshtml
<script src="~/Scripts/ng2/polyfills.js"></script>
<script src="~/Scripts/ng2/vendors.js"></script>
<script src="~/Scripts/ng2/module1.js"></script>
<my-angular-app>
Loading...
</my-angular-app>
HomeController.cs
public ActionResult Module1()
{
return View();
}
That is a bit drawback If we deploy to production. Because after the compiled JavaScript was updated, the browser sometimes still keep the old version of the file because of caching. We should have a mechanism to provide a unique name for the files after deployment. There are 3 options for us to do so.
i. html-webpack-plugin
If we work with pure front-end project, webpack provides html-webpack-plugin to take care of it as below. Technically, It will automatically inject the JavaScript file into our view with the unique id.
webpack-html-webpack-plugin.config.js
...
const HtmlWebpackPlugin = require('html-webpack-plugin');
const viewPath = path.resolve(
__dirname,
"../Server/WebApplication/Views/Home"
);
...
entry: {
polyfills: `${entryPath}/polyfill.ts`,
vendors: `${entryPath}/vendor.ts`,
module1: `${module1}/main.ts`
},
output: {
path: corePath,
filename: "[name].[hash].js",
sourceMapFilename: "[name].[hash].js.map"
}
....
plugins: [,
...,
new HtmlWebpackPlugin({
template: viewPath + "/loader.cshtml",
filename: viewPath + "/Module1.cshtml",
inject: false
})
]
And in the same folder with the designated view, we created a cshtml file named loader.cshtml
loader.cshtml
<% for (var chunk in htmlWebpackPlugin.files.chunks) { %>
<script src="<%= htmlWebpackPlugin.files.chunks[chunk].entry %>"></script>
<% } %>
<my-angular-app>
Loading...
</my-angular-app>
And run the npm run build:html as defined on package.json. If it runs successfully, the result will look like that.
#{
ViewBag.Title = "Module 1";
}
<script src="../../Scripts/ng2/vendors.1470de344a0f2260b700.js"></script>
<script src="../../Scripts/ng2/vendors.1470de344a0f2260b700.js"></script>
<script src="../../Scripts/ng2/module1.1470de344a0f2260b700.js"></script>
<my-angular-app>Loading....</my-angular-app>
ii. Defined your own JavaScript version
In our ASP.NET MVC project is a bit different because we used more than one Angular app. So that we defined a version in the class and append it at the end of the file when loading the JS. By doing it, we will make sure the latest will be loaded into the browser. But it is out of this question context so I will not go further. Basically, It is gonna look like below code.
<script src="#string.Format("{0}?v={1}", "~/Scripts/ng2/polyfills.js", VersionHelper.CurrentVersion())</script>
<script src="#string.Format("{0}?v={1}", "~/Scripts/ng2/vendors.js", VersionHelper.CurrentVersion())</script>
<script src="#string.Format("{0}?v={1}", "~/Scripts/ng2/module1.js", VersionHelper.CurrentVersion())</script>
When serving it on browser, it will look like
<script src="~/Scripts/ng2/polyfills.js?v=1.1.0"></script>
<script src="~/Scripts/ng2/vendors.js?v=1.1.0"></script>
<script src="~/Scripts/ng2/module1.js?v=1.1.0"></script>
iii. ASP.NET Bundling
At Zyllem, our team didn't use it because of our JavaScript files is configure inside the model and render it later to the view.
You can just open App_Start\BundleConfig.cs in your project and config a bundle. Let say the name is module1
bundles.Add(new ScriptBundle("~/bundles/module1").Include(
"~/Scripts/ng2/polyfills.js",
"~/Scripts/ng2/vendors.js"
"~/Scripts/ng2/module1.js"));
And render inside the view by doing it.
Index.cshtml
#Scripts.Render("~/bundles/module1")
So that when serving on the browser, it will have the unique token at the end and it is different If you make any changes in the script bundle.
<script src="/bundles/module1?v=2PbUw0z_gh_Ocp_Vz13rdmmBSG6utLJAXm2lThxYAow1"></script>
If everything works fine, you will see the below screenshots.
Update
Add the repository on Github https://github.com/trungk18/mvc5-angular-webpack

Related

Jenkins shared library and jenkinsFiles at the same repo

Is it possible to have a global shared library and jenkinsFiles at the same repo?
I want to have something like
─ root
   ├── all-jenkins-files
   |    └── dir1
| └── Jenkinsfile1
| └── dir2
| └── Jenkinsfile2
   ├── shared-libraries
      └── src
      └── var
I was trying to use globel shared libraries configuration but I believe it failed because of the directories structure. global shared-libraries expecting to have src and var
folders under root dir.
any idea how to overcome this?
Yes It's possible to have a shared library like structure in the same folder, but I don't think you can use like a shared library (implicit or dynamic loading).
With this kind of scenario, you can make use of load DSL.
If the folder in SCM like :
.
├── shared-library
│   ├── src
│   └── vars
│   ├── log.groovy
│   ├── myPipeline.groovy
│  
├── all-jenkins-file
│   └── Jenkinsfile
shared-library/vars/myPipeline.groovy
stage('01') {
echo "01"
}
stage('02') {
echo "02"
}
stage('03') {
echo "03"
}
shared-library/vars/log.groovy
def info(message) {
echo "INFO: ${message}"
}
def warning(message) {
echo "WARNING: ${message}"
}
return this;
all-jenkins-file/Jenkinsfile
node {
checkout scm
load "${env.WORKSPACE}/shared-library/vars/myPipeline.groovy"
def log =load "${env.WORKSPACE}/shared-library/vars/log.groovy"
log.info('Hello')
}

How can I copy files from parameterised directory using maven archetypes?

I need to copy files from a parameterised local directory to a specific directory inside the project. I currently have this archetype-metadata.xml.
<?xml version="1.0" encoding="UTF-8"?>
<archetype-descriptor
xsi:schemaLocation="http://maven.apache.org/plugins/maven-archetype-plugin/archetype-descriptor/1.0.0 http://maven.apache.org/xsd/archetype-descriptor-1.0.0.xsd"
xmlns="http://maven.apache.org/plugins/maven-archetype-plugin/archetype-descriptor/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
name="custom-maven-archetype">
<fileSets>
<fileSet filtered="true" packaged="true" encoding="UTF-8">
<directory>src/main/java</directory>
</fileSet>
<fileSet filtered="true" packaged="true" encoding="UTF-8">
<directory>src/test/java</directory>
</fileSet>
<fileSet filtered="true" packaged="true" encodind="UTF-8">
<directory>${wsdlFile}</directory>
<outputDirectory>${project.basedir}</outputDirectory>
</fileSet>
</fileSets>
<requiredProperties>
<requiredProperty key="codigoMunicipal" />
<requiredProperty key="wsdlFile" />
</requiredProperties>
</archetype-descriptor>
This is my current filetree.
.
├── pom.xml
├── src
│ └── main
│ └── resources
│ ├── META-INF
│ │ └── maven
│ │ └── archetype-metadata.xml
│ └── archetype-resources
│ ├── README.md
│ └── pom.xml
└── target
├── agency-lib-archetype-1.0.0.jar
└── classes
├── META-INF
│ └── maven
│ └── archetype-metadata.xml
└── archetype-resources
├── README.md
└── pom.xml
You can also use the archetype-post-generate.groovy to copy it manually
See the Post-Generation Script at the end of this page https://maven.apache.org/archetype/maven-archetype-plugin/advanced-usage.html
AS suggest #Devendra, something like this in your groovy script:
def command = "cp ${request.getOutputDirectory()}/subfolderName ./destinationFolder"
def proc = command.execute()
proc.waitFor()
if(proc.exitValue() == 0) {
println "Post Script: Moving files...SUCCESS";
} else{
println "Std Err: ${proc.err.text}"
"Post Script: ... KO, error moving files"
}
Where request is the instance of https://maven.apache.org/archetype/archetype-common/apidocs/org/apache/maven/archetype/ArchetypeGenerationRequest.html attached to this generation.
Or simply use variables on groovyscript:
def command = "cp ./${artifactId}/subfolderName ./destinationFolder"
def proc = command.execute()
proc.waitFor()
if(proc.exitValue() == 0) {
println "Post Script: Moving files...SUCCESS";
} else{
println "Std Err: ${proc.err.text}"
"Post Script: ... KO, error moving files"
}

Jenkins Job DSL - load groovy library from git repo

I want to keep my seed job as small as possible and keep all the logic in a central git repository. Also, I have several independent Jenkins instances that then could share the code. How can I load a groovy library in a Jenkins Job DSL script?
Is there something like the Pipeline Remote File Loader Plugin, so that you only need to do fileLoader.fromGit('lib.groovy', 'https://git.repo')?
Hereafter my quicksheet about achieving this in a parameterized Pipeline job,
Using Pipeline script from SCM from git.repo
What may be of interest to you:
loading mecanism : stash/unstash
"from SCM" location : src = "../${env.JOB_NAME}#script/"
Jenkins
Pipeline
Definition: "Pipeline script from SCM"
SCM: Git
Repository URL git.repo
Branches to build */master
Script Path jobs/build.groovy
This project is parameterized:
String Parameter PARAM0
String Parameter PARAM1
git.repo
├── jobs
│ ├── helpers
│ │ └── utils.groovy
│ └── build.groovy
└── scripts
├── build
│ └── do_build.sh
└── inc.sh
Contents : utils.groovy
├── jobs
│ ├── helpers
│ │ └── utils.groovy
def log(msg) {
println("========== " + msg)
}
return this
Contents : build.groovy
├── jobs
│ └── build.groovy
stage ('Init') {
/* Loads */
def src = "../${env.JOB_NAME}#script/"
def helpers_dir = 'jobs/helpers'
def scripts_dir = 'scripts'
/* Stages Scripts */
def do_build = 'build/do_build.sh'
utils = load src + helpers_dir + "/utils.groovy"
dir(src) {
stash name: scripts_dir, includes: "${scripts_dir}/"
}
}
stage ('Build') {
node () {
unstash scripts_dir
build_return = sh (returnStdout: true, script: """
./${scripts_dir}/${do_build} \
"${PARAM0}" \
"${PARAM1}"
""").readLines()
builded = build_return.get(build_return.size()-1).tokenize(',')
utils.log("PARAM0: " + builded[0])
utils.log("PARAM1: " + builded[1])
}
}
Contents : inc.sh
└── scripts
└── inc.sh
#!/bin/sh
## scripts common includes
common=included
Contents : do_build.sh
└── scripts
├── build
│ └── do_build.sh
#!/bin/sh
## includes
. $(dirname $(dirname ${0}))/inc.sh
echo ${common}
## ${0} : PARAM0
## ${1} : PARAM1
echo "${0},${1}"
The Job DSL Gradle Example shows how to maintain DSL code in a Git repository.

ASP.NET 5 Client Side Depdency Management - Bower

I'm trying out the new ASP.NET 5 with MVC 6, and I'm using bower to manage all my client-side dependencies. Everything is working fine.
But I have a question: When I add a dependency (let's say jQuery). It adds both the /dist and /src along with bower configuration files to the /lib folder of wwwroot. How do I make it include just the compiled source for usage? (So I can reference it in my pages via /lib/jquery/jquery.js?
I have recently been playing in this space and following is something that I have tried:
Deleted the .bowerrrc file to enable installing in the default bower_components folder under the project folder rather than under wwwroor\lib as anything under wwwroot tends to get published.
Added "main-bower-files": "2.9.0" to package.json. This package gets all the files mentioned in the main property of each installed package's bower.json files.
Created a gulp task using the above package
gulp.task('copyMainFiles', function () {
return gulp.src(mainBowerFiles(), { base: 'bower_components' })
.pipe(gulp.dest('wwwroot/lib'));
});
Added a postrestore step to your application's project.json file
"scripts": {
"postrestore": "gulp copyMainFiles",
"prepublish": [ "npm install", "bower install", "gulp clean", "gulp min" ]
}
Updated my application's bower.json to copy files which are not listed in main (like some packages do not have min files as main files..ex: jQuery). The following settings are read by main-bower-files:
"overrides": {
"jquery": {
"main": [ "dist/jquery.js", "dist/jquery.min.js" ]
},
"hammer.js": {
"main": [ "hammer.js", "hammer.min.js" ]
},
"bootstrap": {
"main": [
"./dist/js/bootstrap.js",
"./dist/js/bootstrap.min.js",
"./dist/css/bootstrap.css",
"./dist/css/bootstrap.min.css"
]
}
}
Finally had to update the jquery-validation package to use 1.14.0 instead of 1.11.1 as the previous version does not dist folder and indeed no bower.json...

Grunt wiredep not wiring some bower components

I am using yeoman webapp generator to generate a template to kick start of my work. At this moment, my bower.json looks like this
{
"name": "sample-project",
"private": true,
"dependencies": {
"bootstrap-sass": "~3.3.5",
"modernizr": "~2.8.3",
"fontawesome": "~4.3.0",
"jquery.smooth-scroll": "~1.5.5",
"animate.css": "~3.3.0",
"jquery.appear": "*"
},
"overrides": {
"bootstrap-sass": {
"main": [
"assets/stylesheets/_bootstrap.scss",
"assets/fonts/bootstrap/*",
"assets/javascripts/bootstrap.js"
]
}
},
"devDependencies": {
"chai": "~3.0.0",
"mocha": "~2.2.5"
}
}
Now, in cmd prompt I type this while grunt watch is running
bower install bootstrap-datepicker -S
Then I found "bootstrap-datepicker": "~1.4.0" is inserted into the dependencies section, then
<script src="bower_components/bootstrap-datepicker/js/bootstrap-datepicker.js"></script>
will be automatically wiredep into my index.html, it becomes this
<!-- build:js(.) scripts/vendor.js -->
<!-- bower:js -->
.
.
<script src="bower_components/bootstrap-datepicker/js/bootstrap-datepicker.js"></script>
<!-- endbower -->
<!-- endbuild -->
Then I try to install another bower component to my webapp
bower install country-region-selector -S
The next thing happens is I found "country-region-selector": "~0.1.8" is under dependencies section of bower.json like bootstrap-datepicker, however the corresponding
<script src="bower_components/country-region-selector/dist/crs.min.js"></script>
doesn't get wiredep into my index.html.
So my question is why grunt wiredep doesn't work on some bower components? The same thing happens to form.validation Could anybody shed some light on this problem?
grunt-wiredep works in a very specific way. The dependencies should be listed in an array inside the main property in bower.josn like the example you mentioned from bootstrap.
The problem you are facing is probably those packages doesn't have a main property or it is using multiple files in a string not an array.
To fix that, you can always define an override for the packages main property likes the following ...
In your grunt file:
wiredep: {
...
overrides: {
'package-name': {
'main': [
'link-to-css-file.css',
'link-to-js-file.js'
]
},
},
},

Resources