0%

使用webpack构建AngularJs项目:起步

之前使用angularJs来作项目的时候用的是gulp,现在webpack比较火热,尝试用webpack来构建angularJs项目。为啥要搞angularJs?因为工作中的项目是基于angularJs的,想升级到angular,是离不开webpack的,需要一步步过渡,所以第一步是先用webpack构建angularJs项目,以达到弃用gulp和requireJs的目的。开搞!

初始化项目文件夹

先创建项目文件夹webpack-angularjs,然后用yarn初始化包:

1
2
3
$ mkdir webpack-angularjs
$ cd webpack-angularjs
$ yarn init

安装对应的包:

1
2
$ yarn add angular
$ yarn add -D webpack webpack-cli webpack-dev-server

然后创建项目文件夹src,以及入口html文件和js文件:

1
2
3
$ mkdir src
$ touch src/index.js
$ touch src/index.html

然后我们在scr/index.js中构造angular的模块和控制器:

1
2
3
4
5
import angular from 'angular';
const app = angular.module('myApp', ['ngRoute']);
app.controller('testCtrl', function ($scope) {
$scope.test = '33';
})

然后再在src/index.html中写入模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>webpack angularjs</title>
</head>
<body ng-app="myApp">
<div ng-controller="testCtrl">
{{test}}
</div>
</body>
</html>

我们预定了angular的一个模块myApp,以及一个控制器testCtrl,和一个变量:test。如果正常运行,我们应该在页面上看到33这个字符。

配置webpack

接着创建webpack的配置文件:

1
$ touch webpack.config.js

我们需要安装两个插件:

  • clean-webpack-plugin 清理输出区域
  • html-webpack-plugin 用html模板生成html文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const path = require('path');
const {CleanWebpackPlugin} = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
entry: {
app: './src/index.js'
},
mode: "development",
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
},
devtool: "inline-source-map",
devServer: {
contentBase: path.resolve(__dirname, './dist'),
overlay: true,
hot: true,
quiet: true,
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
filename: "index.html",
template: "src/index.html",
inject: "body",
}),
],
};

然后我们在package.json里面预定义运行和打包的命令:

1
2
3
4
5
6
{
"scripts": {
"start": "webpack server",
"build": "webpack --config webpack.config.js"
},
}

我们首先运行下build看下生成的dist文件里面的文件是否正确:

1
$ yarn build

查看dist文件夹:

1
2
3
dist
├── app.bundle.js
└── index.html

然后查看dist/index.html

1
2
3
4
5
<body ng-app="myApp">
<div ng-controller="testCtrl">
{{test}}
</div>
<script defer src="app.bundle.js"></script></body>

基本正确。

我们可以放心的运行起来:

1
$ yarn start

然后打开localhost:8080可以看到页面上正常的输出了。

加一个angular路由

上面这是一个很简单的一步,我们来加个路由试试看。

首先安装angular的路由模块:

1
$ yarn add angular-route

涉及到路由的话就需要谈下路由的方式了。angularJs有两种路由模式:

  • 标签模式 (hashbang)标签模式是html5模式的降级模式,url路径以#符号开头:http://xxx.com/#!/login
  • html5模式 这种模式看起来和普通的url模式一样:http://xxx.com/login

如果需要使用标签模式的话,需要在config里面设置:

1
2
3
4
angular.module('app', ['ngRoute']).config(['$locationProvider', function($locationProvider){
$locationProvider.html5Mode(false);
$locationProvider.hashPrefix('!');
}])

需要使用html5模式的话需要:

1
2
3
angular.module('app', ['ngRoute']).config(['$locationProvider', function($locationProvider){
$locationProvider.html5Mode(true);
}])

并且我们需要在src/index.html中加入base url

1
2
3
4
<html lang="en">
<head>
<base href="/">
</head>

我们选用html5模式,并且加入一个login页面的路由,修改src/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import angular from 'angular';
import ngRoute from 'angular-route'

const app = angular.module('myApp', ['ngRoute']);

app.config(function ($httpProvider, $routeProvider, $locationProvider, $controllerProvider) {
$locationProvider.html5Mode(true)
$routeProvider
.when('/', {
controller: 'homeCtrl',
template: '<p>home page</p>'
})
.when('/login', {
controller: 'loginCtrl',
templateUrl: './login/login.html',
});
});

app.controller('loginCtrl', ['$scope', function($scope) {
$scope.name = 'tony';
}])
app.controller('homeCtrl', ['$scope', function($scope){
$scope.title = 'home';
}])

我们这里给login路由增加一个模板src/login/login.html

1
2
3
4
<div class="login">
<p>login page</p>
{{name}}
</div>

然后修改src/index.html,加上ng-view和两个路由的链接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<base href="/">
<title>webpack angularjs</title>
</head>
<body ng-app="myApp">
<a href="/login">login</a>
<a href="/">home</a>
<div ng-view></div>
</body>
</html>

然后查看运行的页面,发现点击home的时候正常输出,但是点击login的时候,是无效的,并且控制台里面有个login.html的请求是404。

查看下webpack.config.js,我们发现其中devServer -> contentBase设置的是contentBase: path.resolve(__dirname, './dist'),因为dist是webpack的打包路径,我们暂时没有处理任何关于angular模板html的设置,所以在dist目录是没有src/login/login.html的。

我们尝试将contentBase修改为contentBase: path.resolve(__dirname, './src'),,然后重新运行,发现是可以正常运行的。

这是不行的,我们到时候还是需要的是dist目录,而不是src目录。我们需要解决这个问题,有两个思路:

  • 使用html-loader将模板文件捆绑到js文件中,这样的优点是在运行的时候直接从js中拿取模板,而不是再发请求获取模板文件,但缺点是会使打包的js包变大。
  • 使用copy-webpack-plugin将文件按路径复制到dist目录,缺点就是会发出请求获取模板文件。

最佳实践是,关键模板通过html-loader打包到js中,而需要懒加载的通过file-loader来使用require延迟加载。

我们需要安装html-loaderfile-loader

1
$ yarn add -D html-loader file-loader

我们将需要懒加载的模板定义为:xxx.lazy.html,所以重命名src/login/login.htmlsrc/login/login.lazy.html

然后修改webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
const path = require('path');
const {CleanWebpackPlugin} = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const packageJson = require('./package.json');

module.exports = {
entry: {
app: './src/index.js',
vendor: Object.keys(packageJson.dependencies),
},
mode: "development",
output: {
filename: '[name].[contenthash].js',

path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.lazy\.html$/,
use: [
{
loader: 'file-loader',
options: {
name(resourcePath, resourceQuery) {
if (process.env.NODE_ENV === 'development') {
return '[path][name].[ext]';
}
return '[contenthash].[ext]';
},
},
},
],
},
{
test: /\.html$/,
exclude: /\.lazy\.html$/,
use: [
{
loader: "html-loader",
options: {
// 压缩html
minimize: true,
}
},
]
}
]
},
devtool: "inline-source-map",
devServer: {
index: 'index.html',
contentBase: path.resolve(__dirname, './dist'),
overlay: true,
hot: true,
port: 3000,
historyApiFallback: true,
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
filename: "index.html",
template: "src/index.html",
inject: "body",
}),
],
// 抽离公共库
optimization: {
splitChunks: {
chunks: 'initial',
minSize: 20000,
minRemainingSize: 0,
minChunks: 1,
maxAsyncRequests: 30,
maxInitialRequests: 30,
enforceSizeThreshold: 50000,
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
};

这里我们不仅加上了file-loaderhtml-loader,同时也配置了optimization.splitChunks分离公共库,这样方便我们查看生成的包里面的内容。

然后修改src/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import angular from 'angular';
import ngRoute from 'angular-route'
import homeTemplate from './home/index.html';
const app = angular.module('myApp', ['ngRoute']);

app.config(function ($httpProvider, $routeProvider, $locationProvider, $controllerProvider) {

$locationProvider.html5Mode(true)
$routeProvider
.when('/', {
controller: 'homeCtrl',
template: homeTemplate,
})
.when('/login', {
controller: 'loginCtrl',
templateUrl: require('./login/login.lazy.html'),
});
});

app.controller('loginCtrl', ['$scope', function($scope) {
$scope.name = 'tony';
}])
app.controller('homeCtrl', ['$scope', function($scope){
}])

然后在运行的时候,发现点击login按钮没用,没有去加载对应模板。很蛋疼。

尝试了很久,换成了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import angular from 'angular';
import ngRoute from 'angular-route'
import homeTemplate from './home/index.html';
import loginTemplate from './login/login.lazy.html';

const app = angular.module('myApp', ['ngRoute']);

app.config(function ($httpProvider, $routeProvider, $locationProvider, $controllerProvider) {

$locationProvider.html5Mode(true)
$routeProvider
.when('/', {
controller: 'homeCtrl',
template: homeTemplate,
})
.when('/login', {
controller: 'loginCtrl',
templateUrl:loginTemplate,
});
});
app.controller('loginCtrl', ['$scope', function($scope) {
$scope.name = 'tony';
}])
app.controller('homeCtrl', ['$scope', function($scope){
}])

这样,再运行看看,可以看到当点击login链接后,在控制台是加载了login.lazy.html文件的。

Emm。。。实践出真知啊。。。


这只是很简单的一步,很粗糙,但是总归踏出了这一步,下一步直接上ES6!

码字辛苦,打赏个咖啡☕️可好?💘