如何打造一個令人愉悅的前端開發環境(二)

乖小鬼發表於2016-09-04

前情提要

上一篇文章介紹了目前前端比較流行的各種編輯器,以及各種流行的打包方式,最後給了一個Gulp的例子,這個例子還是14年的時候寫的,還有一些可以優化的空間,就不討論了,這篇文章主要講目前火熱的打包構建方式--Webpack的使用方式。

主菜--沒有開胃湯

其實Webpack的入門指導文章非常多,配置方式也各有各樣,這裡我推薦題葉大神的入門級指南--Webpack 入門指迷,如果不知道Webpack是什麼或者不是很清楚各項配置含義的開發者,可以看此文章掃掃盲。畢竟我這篇文章並不是特別基礎。

一、base.js

var path = require('path')
var baseConfig = {
    resolve: {
        extensions: ['', '.js'],
        fallback: [path.join(__dirname, '../node_modules')],
        alias: {
            'src': path.resolve(__dirname, '../src'),
            'assets': path.resolve(__dirname, '../src/assets'),
            'components': path.resolve(__dirname, '../src/components')
        }
    },
    module: {
        loaders: [{
            test: /\.js$/,
            loader: 'babel',
            exclude: /node_modules/
        }, {
            test: /\.(png|jpe?g|gif|svg|woff2?|eot|ttf|otf)(\?.*)?$/,
            loader: 'url?limit=8192&context=client&name=[path][name].[hash:7].[ext]'
        },
        {
            test: /\.css$/,
            loader: 'style!css!autoprefixer',
        },
        {
            test: /\.scss$/,
            loader: 'style!css!autoprefixer!sass'
        }]
    }
};

module.exports = baseConfig;複製程式碼

解讀下這個基本配置:

1、resolve 解析模組依賴的時候,受影響的配置項。

  • extensions 決定了哪些檔案字尾在引用的時候可以省略點,Webpack幫助你補全名稱。
  • fallback 當webpack在 root(預設當前資料夾,配置時要絕對路徑) 和 modulesDirectories(預設當前資料夾,相對路徑)配置下面找不到相關modules,去哪個資料夾下找modules
  • alias 這個大家應該比較熟悉,requirejs之類的都有,就是別名,幫助你快速指向檔案路徑,少寫不少程式碼,而且不用關心層級關係,需要注意的是:在scss之類的css預編譯中引用要加上~,以便於讓loader識別是別名引用路徑。

2、module 解析不同檔案使用哪些loader,這個比較簡單,很多文章都有,就不多說了,注意的是,這裡的scss可以換成你自己的預編譯器,例如:sass、less、stylus等,或者直接用postcss都行,當然還可以用一種通用方法,後面補上。

二、開發環境配置--config

var webpack = require('webpack');
var path = require('path')
var merge = require('webpack-merge')
var baseConfig = require('./webpack.base')
var getEntries = require('./getEntries')

var hotMiddlewareScript = 'webpack-hot-middleware/client?reload=true';

var assetsInsert = require('./assetsInsert')

module.exports = merge(baseConfig, {
  entry: getEntries(hotMiddlewareScript),
  devtool: '#eval-source-map',
  output: {
    filename: './[name].[hash].js',
    path: path.resolve('./dist'),
    publicPath:'./dist'
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: '"development"'
      }
    }),
    new webpack.optimize.OccurenceOrderPlugin(),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoErrorsPlugin(),
    new assetsInsert()
  ]
})複製程式碼

說說這個配置中的一些難點:

1、getEntries 是用來配置入口檔案,一般很多人是自己手寫,或者SPA頁面,只有一個入口, 很容易就寫出來,但是公司中,很多情況,是需要多入口,也就是多路由的Url,這個時候入口的配置就比較麻煩,我這裡是放單獨一個檔案裡面配置,我們公司是靠規定來執行,也就是一個資料夾所有的main.js都認為是入口檔案,其他都忽略。

function getEntry(hotMiddlewareScript) {
    var pattern = paths.dev.js + 'project/**/main.js';
    var array = glob.sync(pattern);
    var newObj = {};

    array.map(function(el){
        var reg = new RegExp('project/(.*)/main.js','g');
        reg.test(el);
        if (hotMiddlewareScript) {
            newObj[RegExp.$1] = [el, hotMiddlewareScript];
        } else {
            newObj[RegExp.$1] = el;
        }
    });
    return newObj;
}複製程式碼

2、assetsInsert 是用來做模板替換的,一個小外掛把template裡面的值替換成打包後的css或者js。

三、打包環境配置--production

var webpack = require('webpack');
var path = require('path')
var merge = require('webpack-merge')
var baseConfig = require('./webpack.base')
var getEntries = require('./getEntries')
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var assetsInsert = require('./assetsInsert')

var productionConf = merge(baseConfig, {
    entry: getEntries(),
    output: {
        filename: './[name].[hash].js',
        path: path.resolve('./public/dist'),
        publicPath: './'
    },
    plugins: [
        new webpack.DefinePlugin({
            'process.env': {
                NODE_ENV: '"production"'
            }
        }),
        new ExtractTextPlugin('./[name].[hash].css', {
            allChunks: true
        }),
        new webpack.optimize.UglifyJsPlugin({
            compress: {
                warnings: false
            }
        }),
        new webpack.optimize.OccurenceOrderPlugin(),
        new assetsInsert()
    ]
})

productionConf.module.loaders = [
             {
                test: /\.js$/,
                loader: 'babel',
                exclude: /node_modules/
            }, {
                test: /\.(png|jpe?g|gif|svg|woff2?|eot|ttf|otf)(\?.*)?$/,
                loader: 'url?limit=8192&context=client&name=[path][name].[hash:7].[ext]'
            },
            {
                test: /\.css$/,
                loader: ExtractTextPlugin.extract('style', 'css'),
            },
            {
                test: /\.scss$/,
                loader: ExtractTextPlugin.extract('style', 'css!sass')
            }]

module.exports = productionConf複製程式碼

基本跟開發的差不多,差別在於: 1、使用ExtractTextPlugin 來打包css,所以要幹掉原來base的loaders,重新寫了一個,在最下面。

2、UglifyJsPlugin 給js壓縮程式碼。其他沒有什麼好解釋的了,一樣的。

四、構建命令

require('shelljs/global')
env.NODE_ENV = 'production'
var ora = require('ora')
var webpack = require('webpack')
var webpackConfig = require('./webpack.production.config')

var spinner = ora('building for production...')
spinner.start()

var staticPath = __dirname + '/../public/dist/'
rm('-rf', staticPath)
mkdir('-p', staticPath)

webpack(webpackConfig, function (err, stats) {
  spinner.stop()
  if (err) throw err
  process.stdout.write(stats.toString({
    colors: true,
    modules: false,
    children: false,
    chunks: false,
    chunkModules: false
  }) + '\n')
})複製程式碼

寫一個build.js,然後在package.json裡面新增 script 引數

"build": "node build.js"//這裡記得寫自己build.js路徑複製程式碼

甜點(馬卡龍)--有點膩

上面的配置是可以更改的,例如你在loaders 裡面加上

{
  test: /\.vue$/,
  loader: 'vue'
}複製程式碼

就可以變成支援.vue檔案的vuejs打包構建,同理,修改下支援jsx,和新增一些reactjs的module,就可以用來跑Reactjs的東西。

還有可以隨意更改Css預編譯器的型別,用你自己喜歡就行,或者跟我們前面提到的方法,把所有型別都配置上,

var path = require('path')
var config = require('../config')
var ExtractTextPlugin = require('extract-text-webpack-plugin')

exports.assetsPath = function (_path) {
  return path.posix.join(config.build.assetsSubDirectory, _path)
}

exports.cssLoaders = function (options) {
  options = options || {}
  // generate loader string to be used with extract text plugin
  function generateLoaders (loaders) {
    var sourceLoader = loaders.map(function (loader) {
      var extraParamChar
      if (/\?/.test(loader)) {
        loader = loader.replace(/\?/, '-loader?')
        extraParamChar = '&'
      } else {
        loader = loader + '-loader'
        extraParamChar = '?'
      }
      return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '')
    }).join('!')

    if (options.extract) {
      return ExtractTextPlugin.extract('vue-style-loader', sourceLoader)
    } else {
      return ['vue-style-loader', sourceLoader].join('!')
    }
  }

  // http://vuejs.github.io/vue-loader/configurations/extract-css.html
  return {
    css: generateLoaders(['css']),
    postcss: generateLoaders(['css']),
    less: generateLoaders(['css', 'less']),
    sass: generateLoaders(['css', 'sass?indentedSyntax']),
    scss: generateLoaders(['css', 'sass']),
    stylus: generateLoaders(['css', 'stylus']),
    styl: generateLoaders(['css', 'stylus'])
  }
}

// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) {
  var output = []
  var loaders = exports.cssLoaders(options)
  for (var extension in loaders) {
    var loader = loaders[extension]
    output.push({
      test: new RegExp('\\.' + extension + '複製程式碼
), loader: loader }) } return output }

這就是把所有的css預編譯的都加到配置裡面了。

總結下--買單啦

Webpack多種多樣,就算一個loaders都有好幾種不同的配置,讓人很是頭疼,最關鍵的是很多外掛自己的文件也不清不楚,弄得大家都很迷茫,我的經驗就是多試多測,自己多寫一寫,看命令列列印的錯誤,去找原因,不要一看到報錯就慌了,很多新手最容易犯錯就是一看到報錯就懷疑人生了,一定要看報錯記錄,一般都有提示,按照提示去解決相應問題就好啦。

下一章我們講Nodejs的東東。

相關文章