antd-tools/lib/gulpfile.js 源碼解析

上一篇我們說到了gulp任務流,這一篇繼續(xù)說
查看package.json卖宠,可以看到對于antd-tools用到了以下幾個命令:
"lint:ts": "npm run tsc && antd-tools run ts-lint"
"lint-fix:ts": "npm run tsc && antd-tools run ts-lint-fix"
"dist": "antd-tools run dist"
"compile": "antd-tools run compile"
"predeploy"
pub
prepublish


npm run lint:ts:編譯typescript并檢測typescript語法規(guī)范;
npm run lint-fix:ts:編譯typescript并檢測typescript語法規(guī)范并且根據(jù)配置文件自動格式化代碼;
npm run dist:
首先看dist任務的源碼

function dist(done) {
  rimraf.sync(path.join(cwd, 'dist'));
  process.env.RUN_ENV = 'PRODUCTION';
  const webpackConfig = require(path.join(cwd, 'webpack.config.js'));
  webpack(webpackConfig, (err, stats) => {
    if (err) {
      console.error(err.stack || err);
      if (err.details) {
        console.error(err.details);
      }
      return;
    }

    const info = stats.toJson();

    if (stats.hasErrors()) {
      console.error(info.errors);
    }

    if (stats.hasWarnings()) {
      console.warn(info.warnings);
    }

    const buildInfo = stats.toString({
      colors: true,
      children: true,
      chunks: false,
      modules: false,
      chunkModules: false,
      hash: false,
      version: false,
    });
    console.log(buildInfo);
    done(0);
  });
}

簡單概括來說就是根據(jù)項目目錄的webpack.config.js讓webpack去處理前端資源打包工作,并輸出必要信息祖乳。下面是webpack.config.js源碼:

// This config is for building dist files
const webpack = require('webpack');
const getWebpackConfig = require('antd-tools/lib/getWebpackConfig');
const WebpackBar = require('webpackbar');

// noParse still leave `require('./locale' + name)` in dist files
// ignore is better
// http://stackoverflow.com/q/25384360
function ignoreMomentLocale(webpackConfig) {
  delete webpackConfig.module.noParse;
  webpackConfig.plugins.push(new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/));
}

function addLocales(webpackConfig) {
  let packageName = 'antd-with-locales';
  if (webpackConfig.entry['antd.min']) {
    packageName += '.min';
  }
  webpackConfig.entry[packageName] = './index-with-locales.js';
  webpackConfig.output.filename = '[name].js';
}

function externalMoment(config) {
  config.externals.moment = {
    root: 'moment',
    commonjs2: 'moment',
    commonjs: 'moment',
    amd: 'moment',
  };
}

function usePrettyWebpackBar(config) {
  // remove old progress plugin.
  config.plugins = config.plugins
    .filter((plugin) => {
      return !(plugin instanceof webpack.ProgressPlugin)
        && !(plugin instanceof WebpackBar);
    });

  // use brand new progress bar.
  config.plugins.push(
    new WebpackBar({
      name: '??  Webpack',
      minimal: false,
    })
  );
}

const webpackConfig = getWebpackConfig(false);
if (process.env.RUN_ENV === 'PRODUCTION') {
  webpackConfig.forEach((config) => {
    ignoreMomentLocale(config);
    externalMoment(config);
    addLocales(config);
    usePrettyWebpackBar(config);
  });
}

module.exports = webpackConfig;

可以看到這里針對生產(chǎn)環(huán)境逗堵,進行了以下調(diào)整:
1.忽略momenjs本地化模塊
2.對于momentjs不進行打包,同時設定全局變量眷昆,在runtime通過外部引入
3.增加螞蟻金服antd自己的本地化模塊
4.使用更美觀的webpack進度條


下面再來看看'antd-tools/lib/getWebpackConfig'蜒秤,這里是ant-design的webpack核心配置:

const path = require('path');
const webpack = require('webpack');
const WebpackBar = require('webpackbar');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const deepAssign = require('deep-assign');
const replaceLib = require('./replaceLib');
const postcssConfig = require('./postcssConfig');

module.exports = function (modules) {
  const pkg = require(path.join(process.cwd(), 'package.json'));
  const babelConfig = require('./getBabelCommonConfig')(modules || false);

  const pluginImportOptions = [
    {
      style: true,
      libraryName: pkg.name,
      libraryDirectory: 'components',
    },
  ];

  if (pkg.name !== 'antd') {
    pluginImportOptions.push({
      style: 'css',
      libraryDirectory: 'es',
      libraryName: 'antd',
    });
  }

  babelConfig.plugins.push([
    require.resolve('babel-plugin-import'),
    pluginImportOptions,
  ]);

  if (modules === false) {
    babelConfig.plugins.push(replaceLib);
  }

  const config = {
    devtool: 'source-map',

    output: {
      path: path.join(process.cwd(), './dist/'),
      filename: '[name].js',
    },

    resolve: {
      modules: ['node_modules', path.join(__dirname, '../node_modules')],
      extensions: [
        '.web.tsx',
        '.web.ts',
        '.web.jsx',
        '.web.js',
        '.ts',
        '.tsx',
        '.js',
        '.jsx',
        '.json',
      ],
      alias: {
        [pkg.name]: process.cwd(),
      },
    },

    node: [
      'child_process',
      'cluster',
      'dgram',
      'dns',
      'fs',
      'module',
      'net',
      'readline',
      'repl',
      'tls',
    ].reduce((acc, name) => Object.assign({}, acc, { [name]: 'empty' }), {}),

    module: {
      noParse: [/moment.js/],
      rules: [
        {
          test: /\.jsx?$/,
          exclude: /node_modules/,
          loader: 'babel-loader',
          options: babelConfig,
        },
        {
          test: /\.tsx?$/,
          use: [
            {
              loader: 'babel-loader',
              options: babelConfig,
            },
            {
              loader: 'ts-loader',
              options: {
                transpileOnly: true,
              },
            },
          ],
        },
        {
          test: /\.css$/,
          use: ExtractTextPlugin.extract({
            use: [
              {
                loader: 'css-loader',
                options: {
                  sourceMap: true,
                },
              },
              {
                loader: 'postcss-loader',
                options: Object.assign(
                  {},
                  postcssConfig,
                  { sourceMap: true }
                ),
              },
            ],
          }),
        },
        {
          test: /\.less$/,
          use: ExtractTextPlugin.extract({
            use: [
              {
                loader: 'css-loader',
                options: {
                  sourceMap: true,
                },
              },
              {
                loader: 'postcss-loader',
                options: Object.assign(
                  {},
                  postcssConfig,
                  { sourceMap: true }
                ),
              },
              {
                loader: 'less-loader',
                options: {
                  sourceMap: true,
                },
              },
            ],
          }),
        },
      ],
    },

    plugins: [
      new ExtractTextPlugin({
        filename: '[name].css',
        disable: false,
        allChunks: true,
      }),
      new CaseSensitivePathsPlugin(),
      new webpack.BannerPlugin(`
${pkg.name} v${pkg.version}

Copyright 2015-present, Alipay, Inc.
All rights reserved.
      `),
      new WebpackBar({
        name: '??  Webpack',
        minimal: false,
      }),
    ],
  };

  if (process.env.RUN_ENV === 'PRODUCTION') {
    const entry = ['./index'];
    config.entry = {
      [`${pkg.name}.min`]: entry,
    };
    config.externals = {
      react: {
        root: 'React',
        commonjs2: 'react',
        commonjs: 'react',
        amd: 'react',
      },
      'react-dom': {
        root: 'ReactDOM',
        commonjs2: 'react-dom',
        commonjs: 'react-dom',
        amd: 'react-dom',
      },
    };
    config.output.library = pkg.name;
    config.output.libraryTarget = 'umd';

    const uncompressedConfig = deepAssign({}, config);

    config.plugins = config.plugins.concat([
      new webpack.optimize.UglifyJsPlugin({
        output: {
          ascii_only: true,
        },
        compress: {
          warnings: false,
        },
      }),
      new webpack.optimize.ModuleConcatenationPlugin(),
      new webpack.LoaderOptionsPlugin({
        minimize: true,
      }),
      new webpack.DefinePlugin({
        'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production'),
      }),
    ]);

    uncompressedConfig.entry = {
      [pkg.name]: entry,
    };

    uncompressedConfig.plugins.push(new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('development'),
    }));

    return [config, uncompressedConfig];
  }

  return config;
};

下面是對antd-tools的webpack配置文件的解析
首先:const pkg = require(path.join(process.cwd(), 'package.json'));汁咏,我們拿到項目的package.json文件,然后通過getBabelCommonConfig函數(shù)拿到babel的配置作媚,babel配置如下:

'use strict';

module.exports = function (modules) {
  const plugins = [
    require.resolve('babel-plugin-transform-es3-member-expression-literals'),
    require.resolve('babel-plugin-transform-es3-property-literals'),
    require.resolve('babel-plugin-transform-object-assign'),
    require.resolve('babel-plugin-transform-class-properties'),
    require.resolve('babel-plugin-transform-object-rest-spread'),
  ];
  plugins.push([require.resolve('babel-plugin-transform-runtime'), {
    polyfill: false,
  }]);
  return {
    presets: [
      require.resolve('babel-preset-react'),
      [require.resolve('babel-preset-env'), {
        modules,
        targets: {
          browsers: [
            'last 2 versions',
            'Firefox ESR',
            '> 1%',
            'ie >= 9',
            'iOS >= 8',
            'Android >= 4',
          ],
        },
      }],
    ],
    plugins,
  };
};

之后針對拿到的babel配置攘滩,添加并配置babel-plugin-import插件;
用過ant-design的前端工程師們應該都知道這是一個按需加載的babel插件纸泡,有了它你就可以加載相應使用的組件的樣式和js漂问,而不是將ant-design全部加載打包,節(jié)省了打包后的體積女揭。
之后是一個騷操作:

if (modules === false) {
    babelConfig.plugins.push(replaceLib);
  }

這個操作用node-modules中的.js文件去替換項目中引用的庫文件;
下面是replaceLib代碼:(用到了高深的babel的AST知識)

'use strict';

const { join, dirname } = require('path');
const fs = require('fs');

const cwd = process.cwd();

function replacePath(path) {
  if (path.node.source && /\/lib\//.test(path.node.source.value)) {
    const esModule = path.node.source.value.replace('/lib/', '/es/');
    const esPath = dirname(join(cwd, `node_modules/${esModule}`));
    if (fs.existsSync(esPath)) {
      path.node.source.value = esModule;
    }
  }
}

function replaceLib() {
  return {
    visitor: {
      ImportDeclaration: replacePath,
      ExportNamedDeclaration: replacePath,
    },
  };
}

module.exports = replaceLib;

后面是正式的webpack配置了蚤假,比較常規(guī),挑幾個有特點的說說:
transpileOnly: true,:tsx編譯成jsx或js時吧兔,不寫入文件磷仰,保存在內(nèi)存中,加快編譯速度境蔼。
對于樣式文件灶平,antd-tools還使用了postcssConfig,以下是它的配置

const rucksack = require('rucksack-css');
const autoprefixer = require('autoprefixer');

module.exports = {
  plugins: [
    rucksack(),
    autoprefixer({
      browsers: [
        'last 2 versions',
        'Firefox ESR',
        '> 1%',
        'ie >= 9',
        'iOS >= 8',
        'Android >= 4',
      ],
    }),
  ],
};

可以看到它用了rucksack插件,可以很方便的寫css了.
在plugins中還使用了CaseSensitivePathsPlugin:打包時賦予變量提升優(yōu)化瀏覽器運行性能;
new webpack.BannerPlugin:在每個打包出來的文件寫上版權(quán)信息;
別的配置都是常規(guī)操作箍土,還是很容易看懂的逢享。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市吴藻,隨后出現(xiàn)的幾起案子瞒爬,更是在濱河造成了極大的恐慌,老刑警劉巖调缨,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疮鲫,死亡現(xiàn)場離奇詭異吆你,居然都是意外死亡弦叶,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門妇多,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伤哺,“玉大人,你說我怎么就攤上這事者祖×⒗颍” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵七问,是天一觀的道長蜓耻。 經(jīng)常有香客問我,道長械巡,這世上最難降的妖魔是什么刹淌? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任饶氏,我火速辦了婚禮,結(jié)果婚禮上有勾,老公的妹妹穿的比我還像新娘疹启。我一直安慰自己,他們只是感情好蔼卡,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布喊崖。 她就那樣靜靜地躺著,像睡著了一般雇逞。 火紅的嫁衣襯著肌膚如雪荤懂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天塘砸,我揣著相機與錄音势誊,去河邊找鬼。 笑死谣蠢,一個胖子當著我的面吹牛粟耻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播眉踱,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼挤忙,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了谈喳?” 一聲冷哼從身側(cè)響起册烈,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎婿禽,沒想到半個月后赏僧,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡扭倾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年淀零,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片膛壹。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡驾中,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出模聋,到底是詐尸還是另有隱情肩民,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布链方,位于F島的核電站持痰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏祟蚀。R本人自食惡果不足惜工窍,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一占调、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧移剪,春花似錦究珊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至攻人,卻和暖如春取试,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背怀吻。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工瞬浓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蓬坡。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓猿棉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親屑咳。 傳聞我的和親對象是個殘疾皇子萨赁,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容

  • TypeScript 是微軟開發(fā)和控制的開源項目,我在應用 Anguar 2+ 和 Ionic 2+ 框架開發(fā)系統(tǒng)...
    廖全磊LesterLiao閱讀 1,632評論 0 3
  • 中文翻譯 ng help ng build 構(gòu)建您的應用程序并將其放入輸出路徑(dist /默認情況下)兆龙。 別名:...
    4ea0af17fd67閱讀 2,030評論 0 0
  • TypeScript 是 JavaScript 的一個超集杖爽,主要提供了類型系統(tǒng)和對 ES6 的支持,它由 Micr...
    Gukson666閱讀 8,673評論 3 29
  • 富文本編輯器前前后后迭代了一個多月紫皇,準備往開源的路上走慰安,發(fā)現(xiàn)離開源還有好長的路要走。目前看下來有以下幾步: 1. ...
    Ahungrynoob閱讀 1,039評論 0 3
  • I重述原文: what:片段中教給了我們在管教孩子的過程當中聪铺,如何關注于解決問題化焕。 范圍:當孩子做出了一些錯誤的或...
    付靖翕閱讀 248評論 0 0