blackLearning.github.io
blackLearning.github.io copied to clipboard
从头搭建基于gulp和webpack的兼容es2015的ng1.x项目框架
基于gulp和webpack搭建兼容es2015的ng1.x项目框架
一、目的
基于现有的ng1.x的版本搭建一套es6编写的版本,并配套有基本的代码检查工具,单元测试和端到端测试环境搭建。
二、基本工具
babel
- 用于将es6编写的项目代码,测试代码,甚至配置文件代码转为es5
webpack
- 将所有的静态资源,js文件,测试文件进行模块化处理后打包在一起
- less-loader, css-loader,style-loader打包less文件
- file-loader, url-loader打包图片,字体图标等文件
- raw-loader,打包所有的Html文件为字符串
- babel-loader用于打包es6代码, ng-annotate-loader用于处理ng中的依赖注入
- 其他:1. 将打包后的css文件插入dom中 2. 监听文件变化,重新打包刷新的文件 3. 对样式文件进行无刷新的热替换 4. 打包所有的测试文件
gulp
- 启动webpack
- 搭建一个开发服务器
- 自动生成文件结构
**karma **
- 搭建测试环境,配置测试框架
eslint
- 检查代码,规范团队编码风格
三、项目结构
++ client
++++ assets // 所有的静态资源
++++++ icons
++++++ images
++++++ less
++++ components //所有的组件和指令
++++ modules //所有的页面模块
++++ const // 静态配置常量
++++ util // 所有的工具库
++++ app.js // 应用入口文件
++++ index.html // 应用首页
++ e2e
++ test
++ dist // 编译后的文件
++ *.config.js //各种配置文件
四、具体实现
Babel
首先安装babel及其插件依赖.
"babel-core": "^6.7.7",
"babel-register": "^6.16.3",
"babel-plugin-transform-runtime": "^6.7.5",
"babel-runtime": "^6.6.1",
"babel-preset-es2015": "^6.6.0",
"babel-preset-stage-0": "^6.5.0",
"babel-polyfill": "^6.7.4",
"babel-eslint": "^7.0.0",
"babel-loader": "^6.2.4",
- babel-core. 如果你需要以编程的方式来使用 Babel,可以使用 babel-core 这个包。它允许我们直接在代码中直接使用babel的api来编译代码.
var babel = require("babel-core");
babel.transform("code();", options);
// => { code, map, ast }
- babel-register. 常用的运行 Babel 的方法是通过babel-register。这种方法只需要引入文件就可以运行 Babel,或许能更好地融入你的项目设置。
'use strict';
require("babel-register");
import gulp from 'gulp';
通过在gulpfile.babel.js中引入babel-register,这样允许我们在gulpfile中使用es6的语法来配置gulp任务.
- .babelrc---- babel的配置文件 你可以通过安装插件(plugins)或预设(presets,也就是一组插件)来指示 Babel 去做什么事情。
{
"plugins": ["transform-runtime"],
"presets": ["es2015", "stage-0"]
}
babel-preset-es2015 : 可以帮助我们将es6的代码转为es5的代码.
babel-plugin-transform-runtime, babel-runtime: 为了实现 ECMAScript 规范的细节,Babel 会使用“助手”方法来保持生成代码的整洁。 由于这些助手方法可能会特别长并且会被添加到每一个文件的顶部,因此你可以把它们统一移动到一个单一的“运行时(runtime)”中去。 现在,Babel 会把这样的代码:
class Foo {
method() {}
}
编译成:
import _classCallCheck from "babel-runtime/helpers/classCallCheck";
import _createClass from "babel-runtime/helpers/createClass";
let Foo = function () {
function Foo() {
_classCallCheck(this, Foo);
}
_createClass(Foo, [{
key: "method",
value: function method() {}
}]);
return Foo;
}();
这样就不需要把 _classCallCheck 和 _createClass 这两个助手方法放进每一个需要的文件里去了。
babel-preset-stage-0: JavaScript 还有一些提案,正在积极通过 TC39(ECMAScript 标准背后的技术委员会)的流程成为标准的一部分。 babel-preset-stage-0 插件可以让我们使用正在js中的提案中但尚未成为标准的部分特性.
babel-polyfill: Babel默认只转换新的JavaScript句法(syntax),而不转换新的API,比如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转码。
Babel 用了优秀的 core-js 用作 polyfill,并且还有定制化的 regenerator 来让 generators(生成器)和 async functions(异步函数)正常工作。 要使用 Babel polyfill,首先用 npm 安装它: $ npm install --save babel-polyfill 然后只需要在文件顶部导入 polyfill 就可以了: import "babel-polyfill";
- babel-loader babel和webpack的结合使用.
module: {
loaders: [
{ test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" }
]
}
在babel6之前,babel启用某些默认的转换。然而babel6。默认启用不附带任何转换。您需要显式地告诉它什么转换运行。最简单的方法是通过使用一个预设. babel默认只能只能转换es6的一些新的语法,为了模拟一个完整的ES2015环境,您将需要使用一个polyfill(一种选择是babel-polyfill)来模拟字符串实例等方法。
- babel-eslint babel和eslint的结合使用. 帮助eslint识别一些babel的代码.
.eslintrc.js :
parser: 'babel-eslint',
parserOptions: {
sourceType: 'module'
},
Eslint
http://eslint.cn/docs/rules/ : eslint Rules http://eslint.cn/docs/user-guide/configuring : Eslint Config
依赖
{
"eslint": "^3.8.1",
"eslint-config-airbnb-base": "^9.0.0",
"eslint-friendly-formatter": "^2.0.6",
"eslint-loader": "^1.6.0",
}
- eslint-friendly-formatter 对报错进行友好化输出,方便定位错误原因和位置.
webpack.config.js :
eslint: {
formatter: require('eslint-friendly-formatter')
},
- eslint-config-airbnb-base 使用流行的airbnb风格作为基准风格配置.
.eslintrc.js :
extends: 'airbnb-base',
- eslint-loader
webpack.config.js :
preLoaders: [{
test: /\.js$/,
loader: 'eslint',
include: projectRoot,
exclude: /node_modules/
}],
- .eslintrc.js -- eslint 配置文件
// add your custom rules here
'rules': {
'indent' : 0,
'import/no-unresolved': 0,
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
},
"env": {
"browser": true, // browser 全局变量, 解决window变量报错的问题
"node": true,
"phantomjs": true,
"protractor": true,
"jasmine": true, // 添加所有的 Jasmine 版本 1.3 和 2.0 的测试全局变量。
"es6": true // 支持除了modules所有 ECMAScript 6 特性。
},
Gulp
有了webpack之后完全可以不用gulp,但还是把gulp装进来,方便完成一些其他的自动化的任务。
// 编译生产环境代码
gulp.task('build', ['clean'], (cb) => {
const config = require('./webpack.dist.config');
config.entry.app = paths.entry;
// run webpack
webpack(config, (err, stats) => {
if (err) {
throw new gutil.PluginError("webpack", err);
}
gutil.log("[webpack]", stats.toString({
// output options
colors: colorsSupported,
chunks: false,
errorDetails: true
}));
// 启动一个服务来查看build的资源
serve({
port: process.env.PORT || 8801,
open: true,
server: {
baseDir: proRoot
},
middleware: [
// 用于匹配资源,将所有路由重定向到index.htm
historyApiFallback()
]
});
cb();
});
});
// 启动开发环境
gulp.task('serve', () => {
const config = require('./webpack.dev.config');
config.entry.app = [
// This allows you to add hot reloading into an existing server without webpack-dev-server
'webpack-hot-middleware/client?reload=true',
// application entry point
].concat(paths.entry);
var compiler = webpack(config);
serve({
port: process.env.PORT || 3000,
open: false,
server: {
baseDir: root
},
middleware: [
// 用于匹配资源,将所有路由重定向到index.htm
historyApiFallback(),
// No files are written to disk, it handle the files in memory
webpackDevMiddleware(compiler, {
stats: {
colors: colorsSupported,
chunks: false,
modules: false
},
publicPath: config.output.publicPath
}),
// This allows you to add hot reloading into an existing server without webpack-dev-server
webpackHotMiddleware(compiler)
]
});
});
// 自动生成新的文件模板
// 包括*.ctrl.js, *.html, *.spec.js, *.less
// usage: gulp module --name 'moduleName' --parent 'parentPath'
gulp.task('module', () => {
// 首字母大写
const cap = (val) => {
return val.charAt(0).toUpperCase() + val.slice(1);
};
// yargs用于获取启动参数
const name = yargs.argv.name;
const parentPath = yargs.argv.parent || '';
console.log(name, parentPath);
const destPath = path.join(resolveToComponents(), parentPath, name);
return gulp.src(paths.blankTemplates)
.pipe(template({
name: name,
upCaseName: cap(name)
}))
.pipe(rename((path) => {
path.basename = path.basename.replace('temp', name);
}))
.pipe(gulp.dest(destPath));
});
Webpack
- source-map
启动source-map来方便调试代码。
devtool: 'source-map', // development
- loaders
preLoaders: [{
test: /\.js$/,
loader: 'eslint',
include: projectRoot,
exclude: /node_modules/
}],
loaders: [
{ test: /\.js$/, exclude: [/app\/lib/, /node_modules/], loader: 'ng-annotate!babel' },
{ test: /\.html$/, loader: 'html-withimg-loader!raw' },
{ test: /\.less$/, exclude: /\.module\.less$/, loader: 'style!css-loader!less-loader' },
{ test: /\.module\.less$/, loader: 'style!css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!less-loader' },
{ test: /\.css$/, loader: 'style!css' },
{ test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url?limit=10000&name=./img/[name].[hash:7].[ext]' },
{ test: /\.(woff2?|eot|ttf|otf|svg)(\?.*)?$/, loader: 'url?limit=10000&name=./fonts/[name].[hash:7].[ext]' }
]
这里主要时用了css-loader自带的css-modules功能,帮助实现css模块化。 对.module.less使用css-loader并启用了css-modules,importLoaders=1代码该文件被CSS-loader处理之后还会被Style-loader再次处理一次,localIdentName指定了scope css名字生成的规则,这里是[name]代表组件所在目录名,[local]表示本来定义的css名称,[hash:base64:5]代表由css-loader生成的哈希值。 对普通的less文件依然是全局作用域。
Karma
测试中使用es6的问题主要是要把es6的代码转为es5后才能测试,另外解决覆盖率的问题。
// 预处理
require("karma-coverage"),
require("karma-sourcemap-loader"),
require("karma-webpack")
preprocessors: {
'spec.bundle.js': ['webpack', 'sourcemap']
},
利用预处理器解决测试代码打包转化的问题。
// 处理代码测试覆盖率
preLoaders: [{
test: /\.js$/,
loader: 'isparta',
include: path.resolve(projectRoot, 'client'),
exclude: /\.spec$/,
}],
利用isparta-loader来解决es6代码统计覆盖率的问题。