blackLearning.github.io icon indicating copy to clipboard operation
blackLearning.github.io copied to clipboard

从头搭建基于gulp和webpack的兼容es2015的ng1.x项目框架

Open Fadingvision opened this issue 7 years ago • 0 comments

基于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",
  1. babel-core. 如果你需要以编程的方式来使用 Babel,可以使用 babel-core 这个包。它允许我们直接在代码中直接使用babel的api来编译代码.
var babel = require("babel-core");
babel.transform("code();", options);
// => { code, map, ast }
  1. babel-register. 常用的运行 Babel 的方法是通过babel-register。这种方法只需要引入文件就可以运行 Babel,或许能更好地融入你的项目设置。
'use strict';
require("babel-register");
import gulp from 'gulp';

通过在gulpfile.babel.js中引入babel-register,这样允许我们在gulpfile中使用es6的语法来配置gulp任务.

  1. .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";

  1. babel-loader babel和webpack的结合使用.
module: {
  loaders: [
    { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" }
  ]
}

在babel6之前,babel启用某些默认的转换。然而babel6。默认启用不附带任何转换。您需要显式地告诉它什么转换运行。最简单的方法是通过使用一个预设. babel默认只能只能转换es6的一些新的语法,为了模拟一个完整的ES2015环境,您将需要使用一个polyfill(一种选择是babel-polyfill)来模拟字符串实例等方法。

  1. 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",
}
  1. eslint-friendly-formatter 对报错进行友好化输出,方便定位错误原因和位置.

webpack.config.js :

eslint: {
    formatter: require('eslint-friendly-formatter')
},
  1. eslint-config-airbnb-base 使用流行的airbnb风格作为基准风格配置.

.eslintrc.js :

extends: 'airbnb-base',
  1. eslint-loader

webpack.config.js :

preLoaders: [{
  test: /\.js$/,
  loader: 'eslint',
  include: projectRoot,
  exclude: /node_modules/
}],
  1. .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
  1. source-map

启动source-map来方便调试代码。

devtool: 'source-map',  // development
  1. 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代码统计覆盖率的问题。

Fadingvision avatar Aug 14 '17 16:08 Fadingvision