0%

使用webpack构建AngularJs项目:支持ES6

上一篇 使用webpack构建AngularJs项目:起步 我们使用webpack的html-loaderfile-loader完成了一个简单的路由。现在我们继续在这基础之上加功能。

优化控制器

我们现在的写法是将控制器写在入口js文件里面,这样当控制器多了起来之后肯定不行的,我们需要分离控制器的代码为单独的文件。

首先,我们创建login.controller.js文件:

1
$ touch src/login/login.controller.js

然后在里面定义控制器的代码:

1
2
3
4
5
6
7
8
9
10
import loginTemplate from './login.lazy.html'
loginController.$inject = ['$scope'];
function loginController($scope) {
$scope.name = 'tony';
console.log($scope.name)
}
export default {
loginController,
loginTemplate,
}

可能会感到奇怪,第二行的loginController.$inject = ['$scope']为什么可以这样写?

首先,我们可以在声明loginController之前写这句话是利用了变量提升的特性。然后这里的$inject是angularJs的显式注入声明。

这里需要解释下angularJs有三种依赖注入的方式:

  • 推断式注入声明 如果没有明确的声明,angularJs会假定参数名称就是依赖的名称。但是需要注意的是,这种方式只适用于未经过压缩和混淆的代码,因为angularJs需要原始的参数列表来进行解析需要注入的依赖。
  • 行内注入声明 当我们在声明控制器的函数定义时,可以通过行内传参的形式将依赖传入进去,它可以避免在定义过程中使用临时变量。行内声明的方式允许我们直接传入一个参数数组,而不是一个函数。数组的元素是字符串,它们代表可以被注入到对象中的依赖的名字,最后一个参数就是依赖注入的目标函数本身。
  • 显式注入声明 angularJs提供了$inject属性来显式注入依赖。函数对象的$inject属性是一个数组,数组元素的类型是字符串,他们的值就是需要被注入的依赖。

我们在login.controller.js里面同时也引入了模板文件,然后通过export暴露出去。然后我们编辑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
import angular from 'angular';
import ngRoute from 'angular-route'
import login from './login/login.controller';

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

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

app.controller('loginCtrl', login.loginController)
app.controller('homeCtrl')

这种方式的好处是,我们需要管理login页面的之后直接去login的文件管理即可,用到的模板文件和js代码都在各自的区域里面进行关联。

上ES6!

到现在我们写的都是es5的语法,我们希望支持class相关的东西。那需要用到babel,先安装babelpresetbabel-loader

1
$ yarn add -D @babel/core @babel/preset-env babel-loader

需要注意下,我们这里安装的时候直接是默认的babel7版本,所以对应的是@babel/core,我刚开始的时候安装成了babel-core,走了弯路。。。

@babel/preset-env是一个智能预设,允许使用最新的JavaScript语法,而无需去关心细微之处,更简便而且包更小。

然后创建babel的配置文件:

1
$ touch .babelrc

并写入:

1
2
3
4
5
{
"presets": [
["@babel/preset-env"]
]
}

然后在webpack.config.js中加上对js文件的解析:

1
2
3
4
5
6
7
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
},
]

这样webpack的部分就完成了,我们需要去写一个es6语法的服务来测试下。

我们创建文件src/services/http.services.js

1
touch src/services/http.services.js

然后用es6的class语法创建一个服务:

1
2
3
4
5
6
7
8
9
export default class HttpService {
constructor($http) {
this.$http = $http;
}

login(params) {
return this.$http.get('https://baidu.com');
}
}

我们随便写一个方法login,这个方法里面发出了一个get请求,请求地址是百度(暂时先不管它能不能请求成功。。。)。

然后在src/index.js注册这个服务:

1
2
import httpService from './services/http.service';
app.service('httpService', httpService);

然后在src/login/login.controller.js里面去使用这个服务:

1
2
3
4
5
6
loginController.$inject = ['$scope', 'httpService'];
function loginController($scope, httpService) {
$scope.name = 'tony';
console.log($scope.name)
httpService.login();
}

重新启动yarn start,然后我们访问login路由,可以看到页面正常加载的同时,在控制台这边是有个baidu.com的get请求的,虽然是被跨域拦截了。。。

这证明我们可以在写js文件的时候使用es6的语法了,并且转换也是成功的。

那么也可以打包下看看最后生成的dist目录中是怎样的。

1
$ yarn build

然后找到app.xxxxxxx.js的打包文件,可以找到HttpService的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var HttpService = /*#__PURE__*/function () {
function HttpService($http) {
_classCallCheck(this, HttpService);
this.$http = $http;
}

_createClass(HttpService, [{
key: "login",
value: function login(params) {
return this.$http.get('https://baidu.com');
}
}]);
return HttpService;
}();

可以看到,生成的就是目标js语法。而且这里在运行的时候$http也是正用的推断式注入。nice~

上压缩

我们前面查看的就是打包后的文件,里面还是人可以阅读的,这代表着任何人可以访问网站后可以获取到源代码并分析,所以我们需要将打包后的文件进行压缩。

我们使用的是webpack5,所以直接支持optimization.minimize属性进行设置是否压缩。如果设置为true的话,就使用webpack的TerserPlugin插件或optimization.minimizer中指定的插件进行压缩打包。

webpack5内置了TerserPlugin包,我们无需单独安装,所以直接设置webpack.config.js

1
2
3
4
5
module.exports = {
optimization: {
minimize: true,
},
}

然后再运行yarn start,发现点击链接login的时候,会报错:

1
2
angular.js:138 Uncaught Error: [$injector:modulerr] Failed to instantiate module myApp due to:
Error: [$injector:unpr] Unknown provider: e

果然发生了注入问题。前面在了解依赖注入的时候,确实是当使用推断式注入并且压缩打包的时候,是会有问题的。那我们来解决这尴尬的问题。

要么采用显式注入,要么退回到原来的声明式写法。但这和我们的初衷相差甚远,成年人,我们当然是两者都要!

我们需要babel-plugin-angularjs-annotate,它支持向Babel处理的ES5/ES6代码添加angularJs的依赖注入关系,支持显式注入和典型的隐式注入。

安装babel-plugin-angularjs-annotate

1
$ yarn add -D babel-plugin-angularjs-annotate

然后在.babelrc中添加:

1
2
3
4
5
6
7
8
{
"presets": [
[
"@babel/preset-env"
]
],
"plugins": ["angularjs-annotate"]
}

然后我们需要在src/services/http.service.js中的构造函数中加上"ngInject";的标记:

1
2
3
4
5
6
7
8
9
10
export default class HttpService {
constructor($http) {
"ngInject";
this.$http = $http;
}

login(params) {
return this.$http.get('https://baidu.com');
}
}

然后再运行,就可以看到正常了。

我们也将login.controller.js的显式注入的写法替换为标记的方式:

1
2
3
4
5
6
7
8
9
10
11
import loginTemplate from './login.lazy.html'
function loginController($scope, httpService) {
"ngInject";
$scope.name = 'tony';
console.log($scope.name)
httpService.login();
}
export default {
loginController,
loginTemplate,
}

使用babel-plugin-angularjs-annotate后,它是可以处理一般的隐式注入的,需要特殊加标记的情况为:

  • 导出类并在另一个文件/模块中使用时
  • 声明为对象属性的方法的隐式注入时
  • 导出为一个方法的隐式注入时
  • 箭头函数声明方法赋值给变量时:var x = /* @ngInject */ ($scope) => {};

很方便。


到目前为止,我们的webpack打包angularJs已初见模型了,接下来是处理样式文件以及资源文件,再接再厉!

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