webpack没你想的那么难(三):附件通用工程实战

@songhlc 2017-09-10 14:46:11发表于 yonyouyc/blog 原创连载

前两期的内容相对于比较基础,本期则直接把前两周重新写的统一附件组件做为例子拿出来,讲一下在新建一个第三方库的时候我们应该怎么是要用webpack来打包我们的通用组件。

相关代码

所有的代码都放在 ucloud-ko-fileupload里了

附件组件开发的目的

目前我们在多个项目中均使用到了附件组件有requirejs和es6 import两种引入方式,由于源代码又分散在各自维护在各个不同项目之中,会增加以后的维护难度,所以刚好借此机会把附件组件独立出来,同时要支持requirejs 和es6 import的两种方式引入。

附:关于umd

之前大家应该都听过AMD,CMD和Commonjs,那么什么是UMD。

UMD:通用模块定义,虽然commonjs和amd风格都受到不同人的追捧,但是如果需要做到一套代码同时支持多种引入的方式,来保证AMD和CommonJS和谐相处,同时还支持老式的global作用域下的引用,就需要UMD来完成,举个简单的例子

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD
        define(['jquery'], factory);
    } else if (typeof exports === 'object') {
        // Node, CommonJS-like
        module.exports = factory(require('jquery'));
    } else {
        // Browser globals (root is window)
        root.returnExports = factory(root.jQuery);
    }
}(this, function ($) {
    //    methods
    function func(){};

    //    exposed public method
    return func;
}));

所以为了满足我们项目的不同需要,我们就需要把我们的代码最终打包成UMD的方式来处理(详细后面会用到)

项目目录介绍

config //webpack配置文件
dist //  编译后的目录
src // 组件源代码目录
package.json

src详细代码

src
|--u-fileupload //附件组件核心文件目录
|--app.js
|--index.js
|--index.html

index.js: 附件组件入口文件,通常一个组件都有一个入口文件
app.js: 开发的时候引用的入口文件,app.js引入index.js并配合index.html来进行开发

因为需要区分开发配置和用于打包编译的配置,所以我们把webpack的文件分成3个

config里的详细代码

config
|--webpack.base.confg.js //通用配置
|--webpack.dev.config.js //开发配置
|--webpack.prod.config.js //编译配置

-webpack.base.confg.js 详解

const path = require('path');
const basepath = process.cwd();
const distpath = path.resolve(basepath, "./dist");
const webpack = require('webpack')
const ExtractTextPlugin = require('extract-text-webpack-plugin');
function resolve (dir) {
  return path.join(__dirname, '..', dir)
}
module.exports = {
  entry: [
    './src/index.js' // 打包的入口文件(注意dev里会覆盖这个配置)
  ],
  devtool: 'inline-source-map', //开发态的时候使用inline-srouce-map生成.map文件,方便开发调试
  plugins: [
  // css 插件,把所有引入的css文件合并成一个index.css
    new ExtractTextPlugin({
      filename: 'index.css',
      disable: false,
      allChunks: true,
    })
  ],
  resolve: {
    // 引入模块别名
    alias: {
      '@': resolve('src'),
      // 为什么要加一个lodashmix 参见这篇文章[《webpack打包之体积优化》](http://mp.weixin.qq.com/s/ftMbksKgLPLI_4rYgToN6A)(可能需要微信打开,有兴趣的可以咨询我发出微信里的链接)
      
      'lodashmix': resolve('src/util/lodashmix')
    }
  },
  module: {
    rules: [
    //babelloader 将低版本浏览器不识别的语法进行转换
      {
        test: /\.js$/,
        exclude: /(node_modules)/,
        use: {
          loader: 'babel-loader'
        }
      },
      // htmlloader 读取src下的html文件
      {
        test: /\.html/,
        use: {
          loader: 'html-loader'
        }
      },
      // 加载postcss的autoprefixer插件,具体可以参考[Autoprefixer:一个以最好的方式处理浏览器前缀的后处理程序](http://www.cnblogs.com/aNd1coder/archive/2013/08/12/3252690.html)
      {
        test: /\.(less|css)$/,
        use: ExtractTextPlugin.extract({
          use: ['css-loader', 'less-loader',
            {
              loader: 'postcss-loader',
              options: {
                plugins: [
                  require('autoprefixer')({browsers: [
                    'last 3 versions',
                    'ie >= 9']})
                ]
              }
            }],
          fallback: 'style-loader',
        })
      },
      // 用fileloader加载图片
      {
        test: /\.(png|svg|jpg|gif|swf)$/,
        use: [
          'file-loader'
        ]
      }
    ]
  },
  output: {
  // 打包输出目录,以及打包最终的文件名
    path: distpath,
    filename: 'ucloud-ko-fileupload.js'
  },
};

webpack.dev.conf.js

var config = require('./webpack.base.config'),
  path = require('path'),
  webpack = require('webpack');
  //webpack-merge插件,用于webpack配置文件的合并
var merge = require('webpack-merge')
const HtmlWebpackPlugin = require('html-webpack-plugin');
var Dashboard = require('webpack-dashboard');
var DashboardPlugin = require('webpack-dashboard/plugin');
var dashboard = new Dashboard();

config = merge(config, {

  entry: {
  // 开发态入口文件
    app: './src/app.js'
  },
  // 这里要重点说一下,通常我们打包文件的时候,会把我们依赖的第三方包通过externals的方式引入,这样不会把第三方包一起打包到我们输出的文件里面,开发的时候我们直接在html里通过script src的方式引入了jquery和ko 所以这里这么写
  externals: {
    'knockout': 'window.ko',
    'jquery': 'window.$'
  },
  devtool: 'inline-source-map',
  //这个大家在cpu-portal-fe里应该都了解,使用devserver进行本地proxy
  devServer: {
    quiet: true,
    contentBase: '../dist',
    hot: true,
    proxy: {
      '/cpu-basedocrefer': {
        target: 'http://yc.yonyou.com',
        secure: false,
        changeOrigin: true,
        host: "yc.yonyou.com"
      },
      '/yuncai': {
        target: 'http://yc.yonyou.com',
        secure: false,
        changeOrigin: true,
        host: "yc.yonyou.com"
      },
      '/file1': {
        target: 'http://yc.yonyou.com',
        secure: false,
        changeOrigin: true,
        host: "yc.yonyou.com"
      },
      '/file': {
        target: 'http://yc.yonyou.com',
        secure: false,
        changeOrigin: true,
        pathRewrite: {'^/file': '/file1'},
        host: "yc.yonyou.com"
      },
      '/cpu-score':{
        target: 'http://yc.yonyou.com',
        secure: false,
        changeOrigin: true,
        pathRewrite: {'^/file': '/file1'},
        host: "yc.yonyou.com"
      }
    }
  },
  plugins: [
  //开发的时候需要相应的插件引入html文件,这个在本系列文章(一)中已说明
    new HtmlWebpackPlugin({
      template: './src/index.html',
      title: 'ucloud-ko-select'
    }),
    // new webpack.optimize.CommonsChunkPlugin({
    //   name: 'runtime'
    // }),
    new webpack.HotModuleReplacementPlugin(),
    new DashboardPlugin(dashboard.setData),
    //定义一些本地变量,这样就可以在js里这么写:ko.components.register(process.env.NODE_ENV, xxx)(参考src/index.js)
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('dev')
    })
  ]
})
module.exports = config

webpack.prod.config.js

var config = require('./webpack.base.config'),
  webpack = require('webpack');
var merge = require('webpack-merge')
var CleanWebpackPlugin = require('clean-webpack-plugin')
var uglifyjs = require('uglifyjs-webpack-plugin')
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
// ...
config = merge(config, {
//为了支持umd需要对不同的模式进行配置
  externals: {
    knockout: {
      root: 'ko',
      commonjs: 'knockout',
      commonjs2: 'knockout',
      amd: 'knockout'
    },
    jquery: {
      root: '$',
      commonjs: 'jquery',
      commonjs2: 'jquery',
      amd: 'jquery'
    },
    webuploader: {
      root: 'WebUploader',
      commonjs: 'webuploader',
      commonjs2: 'webuploader',
      amd: 'webuploader'
    }
  },
  output: {
    publicPath: "./",
    // 指定打包成umd格式
    libraryTarget: 'umd'
  },
  devtool: 'source-map',
  plugins: [
  //每次编译前,先清空dist目录下的内容
    new CleanWebpackPlugin(['./dist'],
      {
        root: __dirname ,                 //根目录
        verbose:  true,                  //开启在控制台输出信息
        dry:      false                  //启用删除文件
      }),
      //对文件内容进行压缩
    new webpack.optimize.UglifyJsPlugin({
      beautify: false,
      mangle: {
        screw_ie8: true,
        keep_fnames: true
      },
      compress: {
        screw_ie8: true
      },
      comments: false,
      sourceMap: true
    }),
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('production'),
      'process.env.FILE_TAG_NAME':JSON.stringify('ko-fileupload'),
      'process.env.FILE_TAG_NAME_INTABLE':JSON.stringify('ko-fileupload-intable')
    }),
    // new BundleAnalyzerPlugin()
  ]
})
module.exports = config


开发中的一些关键点

1.自定义变量

process.env.xxx

原来代码里经常出现如

if(window.location.href.indexOf('yonyoucloud.com'){
    enterpriseId= 30
} else {
    enterpriseId= 45
}

这种不同租户id判断就可以改成使用process.env.eviroment来判断的,打包给本地和yc环境用的时候配置成30 正式环境再配置成45即可

enterpriseId=process.env.eviroment

代码逻辑就更清晰了

2.引入图片
开发组件和开发项目不同,图片处理起来会有问题,最好是引入小图片并转成base64格式的方式引入,否则使用相对路径可能会发生找不到图片的情况,写组件的时候要慎重

好了,文章看的太多,不如自己实践一回,希望大家有时间自己实践一下,就不会害怕webpack的这些复杂配置了。

下期则会把一些我们工程中用的webpack配置拿出来详细分析一下,让大家不再惧怕工程化这个东西。