自從webpack 誕生,就開啓了webpack的時代,從其餘的老大哥打包工具過分而來,詳情可看: https://github.com/tstrilogy/...javascript
list of loader: https://webpack.github.io/doc... (關於webpack 全部資源的loader 列表)
webpack doc: https://doc.webpack-china.org/ (webpack的中文文檔)css
本機安裝環境:html
系統: macos nodejs: nodejs8.5 npm: 5.3
上面是系統的搭建開發模板的系統環境。vue
{ "name": "handlerbase-template", "version": "1.0.0", "description": "use handlebars", "author": "longfan.zheng", "private": true, "scripts": { "dev": "node build/dev.server.js", "build": "node build/build.js" }, "dependencies": { "bootstrap": "^3.3.7", "jquery": "^3.2.1" }, "devDependencies": { "autoprefixer": "^6.7.2", "babel-core": "^6.22.1", "babel-loader": "^6.2.10", "babel-plugin-transform-runtime": "^6.22.0", "babel-preset-env": "^1.2.1", "babel-preset-stage-2": "^6.22.0", "babel-register": "^6.22.0", "chalk": "^1.1.3", "compression-webpack-plugin": "^1.0.0", "connect-history-api-fallback": "^1.3.0", "copy-webpack-plugin": "^4.0.1", "css-loader": "^0.26.1", "eslint-friendly-formatter": "^3.0.0", "eventsource-polyfill": "^0.9.6", "express": "^4.14.1", "extract-text-webpack-plugin": "^2.0.0", "file-loader": "^0.10.0", "friendly-errors-webpack-plugin": "^1.1.3", "function-bind": "^1.1.0", "handlebars": "^4.0.10", "handlebars-loader": "^1.6.0", "handlebars-template-loader": "^0.8.0", "html-webpack-plugin": "^2.28.0", "http-proxy-middleware": "^0.17.3", "node-sass": "^4.5.3", "opn": "^4.0.2", "optimize-css-assets-webpack-plugin": "^1.3.0", "ora": "^1.1.0", "rimraf": "^2.6.0", "sass-loader": "^6.0.6", "semver": "^5.3.0", "style-loader": "^0.18.2", "url-loader": "^0.5.7", "webpack": "^2.2.1", "webpack-bundle-analyzer": "^2.9.0", "webpack-dev-middleware": "^1.10.0", "webpack-hot-middleware": "^2.16.1", "webpack-merge": "^2.6.1" }, "engines": { "node": ">= 4.0.0", "npm": ">= 3.0.0" }, "browserslist": [ "> 1%", "last 2 versions", "not ie <= 8" ] }
上面是關於開發環境的package.json
的基本內容,關於依賴就是jquery,bootstrap.其餘的開發依賴等待稍後配置說明.java
package.json
進行安裝:$ npm install
或者node
$ yarn
安裝好配置準備以下目錄:jquery
|- build 腳本文件位置 |- config 配置文件 |- dist 打包後的文件 |- node_modules node模塊 |- src 源碼文件 |- static 靜態資源(這個是直接複製到打包目錄的) |- .gitignore git忽略文件目錄 |- home.html 普通html文件 |- home.js html入口js文件 |- index.html 同上 |- index.js 同上 |- package.json |- package-lock.json |- readme.md
文件目錄配置如上說明如上。webpack
const path = require('path') const fs = require('fs') let htmls = fs.readdirSync(path.resolve(__dirname, '..')) .filter(cv => cv.endsWith('.html')) // 獲得root目錄下的全部html文件 const suffix = '.vm' // 定義打包後的文件後綴 const files = {} // 定義輸出文件的對象 htmls = htmls.map(function (html) { const name = html.split('.')[0] files[name] = path.resolve(__dirname, '../dist', name + suffix) }) // 獲得輸出文件的名稱以及路徑 module.exports = { build: Object.assign({ env: require('./prod.env'), assetsRoot: path.resolve(__dirname, '../dist'), assetsSubDirectory: 'static', // 定義靜態資源的子目錄 assetsPublicPath: '/', // 定義資源的路徑 productionSourceMap: true, // 定義產品環境開啓sourceMap productionGzip: true, // 開啓產品環境gzip 壓縮 productionGizpExtensions: ['js', 'css'], // 定義產品環境gzip壓縮的後綴 bundleAnlyzerReport: false //關閉打包後自動打開打包分析報告 }, files), // 打包文件的內容 dev: { env: require('./dev.env'), port: 8080, // 定義開發服務器的端口 autoOpenBrowser: true, // 定義是否自動打開瀏覽器 assetsSubDirectory: 'static', // 定義資源子目錄和build的key 定義同樣 assetsPublicPath: '/', // 同上 proxyTable: {}, // 代理配置 cssSourceMap: false // 關閉cssSourceMap } // 開發配置項 }
說明: 最後打包出來的文件須要使用volcityjs模板引擎來進行服務器渲染,so 打包的文件最後的即爲是xxx.vm文件。須要當前的nodejs 環境支持array.filter方法以及array.map方法git
dev.env.jsgithub
const merge = require('webpack-merge') // webpack 配置文件合併的模塊 const proEnv = require('./prod.env') // 請求產品環境的環境變量 module.exports = merge(proEnv, { NODE_ENV: '"development"' }) // 覆蓋NODE_ENV的值
prod.env.js
module.exports = { NODE_ENV: '"production"' } // 定義產品環境的NODE_ENV
上面就就定義好了開發的配置文件,主要是關於打包配置項以及開發配置項的定義。
webpack.base.conf.js (基本的webpack 配置文件)
const path = require('path') const config = require('../config') // 獲取index.js const fs = require('fs') const webpack = require('webpack') function resolve (dir) { return path.join(__dirname, '..', dir) } // 解析根目錄的方法,返回一個絕對路徑 function assetsPath (_path) { var assetsSubDirectory = process.env.NODE_ENV === 'production' ? config.build.assetsSubDirectory : config.dev.assetsSubDirectory return path.posix.join(assetsSubDirectory, _path) } // 經過當前NODE_ENV獲得資源路徑 const prefix = './' // 定義路徑淺醉 let jsFiles = fs.readdirSync(path.resolve(__dirname, '..')) .filter(function (cv) { return cv.endsWith('.js') }) // 獲得入口文件,在根目錄的js的文件,該項目中就是home.js和index.js let entries = {} jsFiles.map(function (cv) { const name = cv.split('.')[0] entries[name] = prefix + cv return cv }) // 獲得一個{ 'home': 'home.js', ...}這樣的對象 module.exports = { entry: entries, // 配置入口文件 output: { path: config.build.assetsRoot, filename: '[name].js', publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath }, // 配置輸出 resolve: { extensions: ['.js', '.hbs', '.json'], alias: {} }, // 配置require.resolve的解析,能夠直接使用require('main.js')或者require('main.hbs')或者require('main.json')均可以省略後綴 devtool: 'source-map', module: { rules: [ // 配置loader { test: /\.js$/, loader: 'eslint-loader', enforce: 'pre', include: [resolve('src')], options: { formatter: require('eslint-friendly-formatter') } }, { test: /\.hbs$/, // 在這裏配置了hbs後綴的文件解析。 loader: 'handlebars-loader', query: {} }, { test: /\.js$/, loader: 'babel-loader', include: [resolve('src')] }, { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: assetsPath('img/[name].[hash:7].[ext]') } }, { test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: assetsPath('media/[name].[hash:7].[ext]') } }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: assetsPath('fonts/[name].[hash:7].[ext]') } }, { test: /\.(css|scss)$/, // 這裏配置sass的解析。 use: [ { loader: 'style-loader' }, { loader: 'css-loader', options: { sourceMap: true } }, { loader: 'sass-loader', options: { sourceMap: true } } ] } ] }, plugins: [ new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery' }) ] // 這裏定義了jquery能夠直接在整個項目中使用$或者jQuery,不須要定義 }
webpack.dev.conf.js
const webpack = require('webpack') const path = require('path') const merge = require('webpack-merge') const config = require('../config') const baseWebpackConfig = require('./webpack.base.conf') // 導入base const HtmlWebpackPlugin = require('html-webpack-plugin') // 導入html 解析的webpack的插件 const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') // 導入友好的錯誤提示的插件 const fs = require('fs') Object.keys(baseWebpackConfig.entry).forEach(function (name) { baseWebpackConfig.entry[name] = ['./build/dev.client'].concat( baseWebpackConfig.entry[name]) }) // 自動讀取在根目錄下的html文件,並使用webpack的插件進行解析 let htmls = fs.readdirSync(path.resolve(__dirname, '..')) .filter(cv => cv.endsWith('.html')) const prefix = './' htmls = htmls.map(function (html) { return new HtmlWebpackPlugin({ filename: prefix + html, template: prefix + html, inject: true, chunks: [html.split('.')[0]] }) }) // 獲得一個HtmlWebpackPlugin 的對象,具體配置看webpack-html-plugin主頁 module.exports = merge(baseWebpackConfig, { devtool: '#cheap-module-eval-source-map', plugins: [ new webpack.DefinePlugin({ 'process.env': config.dev.env }), new webpack.HotModuleReplacementPlugin(), new webpack.NoEmitOnErrorsPlugin(), new FriendlyErrorsPlugin() ].concat(htmls) }) // 經過webpack-merge 合併配置文件
webpack.prod.conf.js
const path = require('path') const webpack = require('webpack') const config = require('../config') const merge = require('webpack-merge') const baseWebpackConfig = require('./webpack.base.conf') const CopyWebpackPlugin = require('copy-webpack-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin') const ExtractTextPlugin = require('extract-text-webpack-plugin') const OptimizeCssPlugin = require('optimize-css-assets-webpack-plugin') const fs = require('fs') const env = process.env.NODE_ENV === 'testing' ? require('../config/test.env') : config.build.env function assetsPath (_path) { var assetsSubDirectory = process.env.NODE_ENV === 'production' ? config.build.assetsSubDirectory : config.dev.assetsSubDirectory return path.posix.join(assetsSubDirectory, _path) } let htmls = fs.readdirSync(path.resolve(__dirname, '..')) .filter(cv => cv.endsWith('.html')) const prefix = './' htmls = htmls.map(function (html) { const name = html.split('.')[0] return new HtmlWebpackPlugin({ filename: config.build[name], template: prefix + html, inject: true, chunks: [name, 'vendor', 'manifest'], minify: { removeComments: true, collapseWhitespace: true, removeAttributeQuotes: true }, chunksSortMode: 'dependency' }) }) const webpackConfig = merge(baseWebpackConfig, { devtool: config.build.productionSourceMap ? '#source-map' : false, output: { path: config.build.assetsRoot, filename: assetsPath('js/[name].[chunkhash].js'), chunkFilename: assetsPath('js/[id].[chunkhash].js') }, plugins: [ new webpack.DefinePlugin({ 'process.env': env }), new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false }, sourceMap: true }), new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', minChunks: function (module, count) { return ( module.resource && /\.js$/.test(module.resource) && module.resource.indexOf( path.join(__dirname, '../node_modules') ) === 0) } }), new webpack.optimize.CommonsChunkPlugin({ name: 'manifest', chunks: ['vendor'] }), new CopyWebpackPlugin([ { from: path.resolve(__dirname, '../static'), to: config.build.assetsSubDirectory, ignore: ['.*'] } ]) ].concat(htmls) }) if (config.build.productionGzip) { const CompressionWebpackPlugin = require('compression-webpack-plugin') webpackConfig.plugins.push( new CompressionWebpackPlugin({ asset: '[path].gz[query]', algorithm: 'gzip', test: new RegExp(( '\\.(' + config.build.productionGizpExtensions.join('|') + ')$' )), threshold: 10240, minRatio: 0.8 }) ) } if (config.build.bundleAnlyzerReport) { const BundleAnalyzerPlugin = require( 'webpack-bundle-analyzer').BundleAnalyzerPlugin webpackConfig.plugins.push(new BundleAnalyzerPlugin()) } module.exports = webpackConfig
開發環境的打包文件主要是開啓壓縮功能,能夠將node_modules的文件打包一個文件,以及開啓壓縮等等.
編寫開發服務器腳本
const config = require('../config') if (!process.env.NODE_ENV) { process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) } const opn = require('opn') const path = require('path') const express = require('express') const webpack = require('webpack') const proxyMiddleware = require('http-proxy-middleware') const webpackConfig = process.env.NODE_ENV === 'testing' ? require( './webpack.prod.conf') : require('./webpack.dev.conf') const port = process.env.PORT || config.dev.port const autoOpenBrowser = !!config.dev.autoOpenBrowser const proxyTable = config.dev.proxyTable const app = express() const complier = webpack(webpackConfig) const devMiddleware = require('webpack-dev-middleware')(complier, { publicPath: webpackConfig.output.publicPath, quiet: true, noInfo: false }) // 開啓開發中間件 const hotMiddleware = require('webpack-hot-middleware')(complier, { log: () => console.log, heartbeat: 2000 }) // 開啓熱更新 complier.plugin('compilation', function (compilation) { compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { hotMiddleware.publish({action: 'reloadd'}) cb() }) }) Object.keys(proxyTable).forEach(function (context) { const options = proxyTable[context] if (typeof options === 'string') { options = {target: options} } app.use(proxyMiddleware(options.filter || context, options)) }) app.use(require('connect-history-api-fallback')()) // 攔截get請求 app.use(devMiddleware) app.use(hotMiddleware) const staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) app.use(staticPath, express.static('./static')) // 設置靜態資源目錄和上訴config目錄中配置同樣 const uri = 'http://localhost:' + port let _resolve const readyPromise = new Promise(resolve => { _resolve = resolve }) console.log('> Starting dev server...') devMiddleware.waitUntilValid(() => { console.log('> Listening at ' + uri + '\n') if (autoOpenBrowser && process.env.NODE_ENV !== 'test') { opn(uri) } _resolve() }) const server = app.listen(port) module.exports = { ready: readyPromise, close: () => { server.close() } }
編寫客戶端熱更新模塊:
require('eventsource-polyfill') const hotClient = require( 'webpack-hot-middleware/client?noInfo=true&reload=true') hotClient.subscribe(function (event) { // console.log('[hot client]', event.action) if (event.action === 'reload') { window.loaction.reload() } })
這裏主要是使用webpack-hot-middleware的語法,而後根據監聽時間刷新頁面
編寫打包腳本build.js
process.env.NODE_ENV = 'production' const ora = require('ora') // 控制檯的等待的控制器 const rm = require('rimraf') // 刪除文件 const path = require('path') const chalk = require('chalk') // 控制檯有顏色輸出 const webpack = require('webpack') const config = require('../config') const webpackConfig = require('./webpack.prod.conf') const spinner = ora('building for production...') spinner.start() rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), function (err) { if (err) throw err 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\n') console.log(chalk.cyan(' Build complete.\n')) console.log(chalk.yellow( ' Tip: built files are meant to be served over an HTTP server.\n' + ' Opening index.html over file:// won\'t work.\n' )) }) })
詳細請看源碼註釋以及文件後面的備註. 源碼請移步: https://github.com/tstrilogy/...(歡迎提issue和點star)
打包後生成的文件:
顯示效果:
均可以寫出hbs文件重複使用.
webpack是一個強大的打包工具,上面的配置文件是依據vue-webpack的配置文件,去除了vue的,改寫成一個僅僅使用handlebars和jquery以及bootstrap的開發模板。固然能夠根據其餘的解析loader搭建不一樣開發環境,都是很容易的。
本文完.