如何打造一個(gè)令人愉悅的前端開(kāi)發(fā)環(huán)境(二)
前情提要
上一篇文章介紹了目前前端比較流行的各種編輯器,以及各種流行的打包方式,***給了一個(gè)Gulp的例子,這個(gè)例子還是14年的時(shí)候?qū)懙?,還有一些可以?xún)?yōu)化的空間,就不討論了,這篇文章主要講目前火熱的打包構(gòu)建方式--Webpack的使用方式。
主菜--沒(méi)有開(kāi)胃湯
其實(shí)Webpack的入門(mén)指導(dǎo)文章非常多,配置方式也各有各樣,這里我推薦題葉大神的入門(mén)級(jí)指南--Webpack 入門(mén)指迷,如果不知道Webpack是什么或者不是很清楚各項(xiàng)配置含義的開(kāi)發(fā)者,可以看此文章掃掃盲。畢竟我這篇文章并不是特別基礎(chǔ)。
一、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;
解讀下這個(gè)基本配置:
1、resolve 解析模塊依賴(lài)的時(shí)候,受影響的配置項(xiàng)。
extensions 決定了哪些文件后綴在引用的時(shí)候可以省略點(diǎn),Webpack幫助你補(bǔ)全名稱(chēng)。
fallback 當(dāng)webpack在 root(默認(rèn)當(dāng)前文件夾,配置時(shí)要絕對(duì)路徑) 和 modulesDirectories(默認(rèn)當(dāng)前文件夾,相對(duì)路徑)配置下面找不到相關(guān)modules,去哪個(gè)文件夾下找modules
alias 這個(gè)大家應(yīng)該比較熟悉,requirejs之類(lèi)的都有,就是別名,幫助你快速指向文件路徑,少寫(xiě)不少代碼,而且不用關(guān)心層級(jí)關(guān)系,需要注意的是:在scss之類(lèi)的css預(yù)編譯中引用要加上~,以便于讓loader識(shí)別是別名引用路徑。
2、module 解析不同文件使用哪些loader,這個(gè)比較簡(jiǎn)單,很多文章都有,就不多說(shuō)了,注意的是,這里的scss可以換成你自己的預(yù)編譯器,例如:sass、less、stylus等,或者直接用postcss都行,當(dāng)然還可以用一種通用方法,后面補(bǔ)上。
二、開(kāi)發(fā)環(huán)境配置--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()
- ]
- })
說(shuō)說(shuō)這個(gè)配置中的一些難點(diǎn):
1、getEntries 是用來(lái)配置入口文件,一般很多人是自己手寫(xiě),或者SPA頁(yè)面,只有一個(gè)入口, 很容易就寫(xiě)出來(lái),但是公司中,很多情況,是需要多入口,也就是多路由的Url,這個(gè)時(shí)候入口的配置就比較麻煩,我這里是放單獨(dú)一個(gè)文件里面配置,我們公司是靠規(guī)定來(lái)執(zhí)行,也就是一個(gè)文件夾所有的main.js都認(rèn)為是入口文件,其他都忽略。
- 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 是用來(lái)做模板替換的,一個(gè)小插件把template里面的值替換成打包后的css或者js。
三、打包環(huán)境配置--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
基本跟開(kāi)發(fā)的差不多,差別在于:
1、使用ExtractTextPlugin 來(lái)打包c(diǎn)ss,所以要干掉原來(lái)base的loaders,重新寫(xiě)了一個(gè),在最下面。
2、UglifyJsPlugin 給js壓縮代碼。其他沒(méi)有什么好解釋的了,一樣的。
四、構(gòu)建命令
- 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')
- })
寫(xiě)一個(gè)build.js,然后在package.json里面添加 script 參數(shù)
- "build": "node build.js"//這里記得寫(xiě)自己build.js路徑
甜點(diǎn)(馬卡龍)--有點(diǎn)膩
上面的配置是可以更改的,例如你在loaders 里面加上
- {
- test: /\.vue$/,
- loader: 'vue'
- }
就可以變成支持.vue文件的vuejs打包構(gòu)建,同理,修改下支持jsx,和添加一些reactjs的module,就可以用來(lái)跑Reactjs的東西。
還有可以隨意更改Css預(yù)編譯器的類(lèi)型,用你自己喜歡就行,或者跟我們前面提到的方法,把所有類(lèi)型都配置上,
- 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預(yù)編譯的都加到配置里面了。
總結(jié)下--買(mǎi)單啦
Webpack多種多樣,就算一個(gè)loaders都有好幾種不同的配置,讓人很是頭疼,最關(guān)鍵的是很多插件自己的文檔也不清不楚,弄得大家都很迷茫,我的經(jīng)驗(yàn)就是多試多測(cè),自己多寫(xiě)一寫(xiě),看命令行打印的錯(cuò)誤,去找原因,不要一看到報(bào)錯(cuò)就慌了,很多新手最容易犯錯(cuò)就是一看到報(bào)錯(cuò)就懷疑人生了,一定要看報(bào)錯(cuò)記錄,一般都有提示,按照提示去解決相應(yīng)問(wèn)題就好啦。