最近是己,發(fā)現(xiàn)自己離前端新技術(shù)接觸越來(lái)越少了,由于用不上任柜,連以前接觸過(guò)的webpack卒废、react這些都開始淡忘沛厨。本著一顆學(xué)習(xí)的心,打算在工作之余摔认,抽取一部分時(shí)間來(lái)鉆研逆皮,搭建一個(gè)簡(jiǎn)單的管理平臺(tái)。
基于技術(shù)上的發(fā)展级野、穩(wěn)定性及目前提倡的自動(dòng)化和模塊化開發(fā)页屠,本人暫時(shí)確定采用webpack/express/react/react-router/redux。
前端自動(dòng)化和模塊化 -- 前端工具webpack的使用
- 在開始之前蓖柔,先看下項(xiàng)目的結(jié)構(gòu)(由于項(xiàng)目采用的是express腳手架來(lái)生成的辰企,后續(xù)添加的內(nèi)容基本以此為基準(zhǔn))
項(xiàng)目目錄結(jié)構(gòu)
Paste_Image.png
public文件夾為前端開發(fā)目錄,dist為打包后目錄况鸣。
webpack配置牢贸。
- views中的html采用的是插件HtmlWebpackPlugin生成的
// 遍歷所有模板中的.html文件,使用HtmlWebpackPlugin引入靜態(tài)資源(或者用ejs模板生成多個(gè))
var htmlfiles = fs.readdirSync(path.resolve(__dirname, "./src/public/templates"));
htmlfiles.forEach(function(item) {
var currentpath = path.resolve(__dirname+"/src/public/templates", item);
var extname = path.extname(currentpath);
if(fs.statSync(currentpath).isFile() && (extname == ".html" || extname == ".ejs")) {
var json = {
template: currentpath, // 設(shè)置引入的模板
filename: path.join(__dirname, './src/views/'+item), // 設(shè)置輸出路徑
inject: 'body', // 設(shè)置js插入位置
hash: true, // 為所有的資源加上hash值
chunks: ["style", "vendor1", "vendor2", "common", "chunk", "bundle"], // 指定引入的資源
showErrors: true // 是否展示錯(cuò)誤
}
// 生產(chǎn)環(huán)境壓縮html
if(isPro) {
json["minify"] = {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
}
}
config.plugins.push(
new HtmlWebpackPlugin(json)
);
}
});
- 設(shè)置環(huán)境變量process.env.NODE_ENV
// 在package.json中設(shè)置
"start": "set NODE_ENV=development && supervisor -i ./src/public ./src/bin/www"
- 采用ExtractTextPlugin和postcss-loader處理樣式镐捧。在js中采用require引入css樣式表潜索,webpack默認(rèn)將其合并到編譯后的文件中,故需要將其抽取出來(lái)懂酱,做轉(zhuǎn)換和兼容處理竹习。
## 在loader中添加
{
// 識(shí)別js中require引入的樣式表,并將其轉(zhuǎn)換和兼容處理
test: /\.(css|scss|sass)$/,
exclude: /node_modules/,
use: ['css-hot-loader'].concat(ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
options: { autoprefixer: true, sourceMap: true, importLoaders: 1 }
},
{
loader: 'postcss-loader',
options: {
sourceMap: true,
plugins: () => [autoprefixer({ browsers: ['last 2 versions', 'safari >= 5', 'ie 8', 'ie 9','iOS >= 7', 'Android >= 4.1'] })],
}
},
'sass-loader'
]
}))
}
## 在plugins中添加
// 抽離樣式
new ExtractTextPlugin({
filename: (getPath) => {
return getPath('css/style.css');
},
allChunks: true
}),
- 用加載器url-loader對(duì)圖片進(jìn)行處理
{
// 圖片加載器列牺,可以將較小的圖片轉(zhuǎn)成base64整陌,減少http請(qǐng)求
// 如下配置,將小于8kb的圖片轉(zhuǎn)成base64碼瞎领,大于8192byte的圖片將通過(guò)file-loader把源文件遷移到指定的路徑泌辫,并返回新的路徑
// [hash]/[name] hash值,防止重名九默,如兩張圖片一樣震放,只會(huì)生成同一hash
test: /\.(png|jpg|gif|svg)$/,
loader: [
'url-loader?limit=8192&name=images/[hash].[ext]',
'image-webpack-loader'
],
- 對(duì)js進(jìn)行轉(zhuǎn)換處理
## loader中添加
// 在新版中,loaders中的加載器驼修,不能以"es3ify!babel"這樣連起來(lái)殿遂,要么數(shù)組,要么"es3ify-loader!babel-loader"
{
// es3ify-loader 兼容ie8乙各,將es5轉(zhuǎn)譯成es3
test: /\.js$/,
exclude: /node_modules/,
loaders: ["es3ify-loader", "babel-loader"]
}
- 設(shè)置自動(dòng)補(bǔ)全文件后綴
resolve: {
extensions: ['*', '.js', '.jsx', ".css", ".scss"] // 值得注意的是勉躺,在新版中,數(shù)組中不能存在''觅丰,而是用'*'替代
}
- 使用插件CommonsChunkPlugin抽取公共依賴模塊
new webpack.optimize.CommonsChunkPlugin({
name: 'chunk',
chunks: ['vendor2', 'bundle'] // 這里是output生成的輸出文件
}),
- 使用插件CopyWebpackPlugin來(lái)拷貝資源,主要用于第三方插件/庫(kù)的拷貝妨退,配合webpack的externals配置妇萄,可直接在html中引用cdn/本地第三方插件/庫(kù)蜕企,而不會(huì)被webpack打包
// 拷貝資源
new CopyWebpackPlugin(
[{
from: './src/public/plugins/',
to: 'plugins'
}]
)
- 使用插件UglifyJsPlugin壓縮代碼
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
mangle: {
except: ['jQuery', '$', 'exports', 'require', "module"]
}
})
- noParse、alias和ProvidePlugin搭配
// 設(shè)置引入的插件別名冠句,重定向到指定的文件轻掩,阻止webpack對(duì)require引用的文件進(jìn)行遍歷,查詢其依賴等
alias: {
react : path.join(__dirname, "node_modules/react/dist/react.min.js"),
"react-dom" : path.join(__dirname, "node_modules/react-dom/dist/react-dom.min.js")
}
// 設(shè)置自動(dòng)引入相應(yīng)模塊功能
// 當(dāng)腳本有引入以下變量時(shí)懦底,會(huì)自動(dòng)加載引入相對(duì)應(yīng)的模塊
new webpack.ProvidePlugin({
React: 'react',
ReactDOM: 'react-dom'
})
// 默認(rèn)的require()方法會(huì)在webpack打包的時(shí)候去解壓唇牧,遍歷ReactJS及其依賴,使用noParse可阻止其默認(rèn)行為
noParse: [
path.join(__dirname, "node_modules/react/dist/react.min.js"),
path.join(__dirname, "node_modules/react-dom/dist/react-dom.min.js")
]
熱更新
作為一個(gè)會(huì)偷懶的前端聚唐,使用到了webpack丐重,怎么能漏掉其熱更新功能呢。webpack的熱更新功能目前主要有兩種配置方式杆查,一種為webpack-dev-server扮惦,另一種為webpack-dev-middleware和webpack-hot-middleware兩個(gè)中間件配合。其實(shí)webpack-dev-server也是采用webpack-dev-middleware這個(gè)中間件來(lái)實(shí)現(xiàn)的亲桦。由于本人后端采用的是node崖蜜,直接忽略掉了webpack-dev-server。
- 先裝個(gè)逼
cnpm intstall webpack-dev-middleware --save-dev
cnpm intstall webpack-hot-middleware --save-dev
- 配置webpack.config.js
var publicPath = "http://127.0.0.1:3000/dist/"; // output中設(shè)置publicPath且publicPath必須為絕對(duì)路徑
var webpackHotMiddleware = 'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=10000&reload=true';
entry: {
vendor1 : ["es5-shim", "es5-sham", "console-polyfill", "babel-polyfill", webpackHotMiddleware],
vendor2 : ["jquery", "react", "react-dom", webpackHotMiddleware],
common: ["commonExt", webpackHotMiddleware],
bundle : ["register", webpackHotMiddleware]
},
output: {
path: path.resolve(__dirname, './src/dist'),
publicPath:publicPath,
filename: 'js/[name].js',
}
- 配置app.js(express的配置文件)
// 最好將其放在路由配置之前
if(process.env.NODE_ENV == "production") {
app.use(express.static(path.join(__dirname, 'dist'),{maxAge:1000*60*60*30}));
app.use(express.static(path.join(__dirname, '../src'),{maxAge:1000*60*60*30}));
app.use(express.static(path.join(__dirname, 'tmp'),{maxAge:1000*60*60*30}));
}else{
var webpack = require("webpack");
let devMiddleWare = require('webpack-dev-middleware');
let hotMiddleWare = require('webpack-hot-middleware');
let webpackconfig = require('../webpack.config.js');
var compiler = webpack(webpackconfig);
app.use(devMiddleWare(compiler,{
publicPath: webpackconfig.output.publicPath,
noInfo: true,
lazy: false,
stats: {
colors: true,
chunks: false
}
}));
- 在入口文件上添加
// 如我的入口文件為register.js
if (module.hot) {
module.hot.accept();
}
- 以上配置只能實(shí)現(xiàn)js的熱更新客峭,css要實(shí)現(xiàn)實(shí)時(shí)刷新效果豫领,就必須引入css-hot-loader
結(jié)語(yǔ)
前奏基本已經(jīng)搞定,剩下的就是一些優(yōu)化了舔琅,不得不感慨前端工具發(fā)展之快等恐,從grunt到gulp再到webpack,前端的打包工具不斷的進(jìn)化搏明,改朝換代的速度讓人不得不感慨鼠锈。每種工具都學(xué)點(diǎn),也懶得深入研究星著,一路挖坑一路填坑购笆,回頭想想,擦虚循,誰(shuí)的鍋M贰!横缔!
附上本人的完整webpack配置
var path = require("path");
var webpack = require("webpack");
var fs = require("fs");
var autoprefixer = require("autoprefixer");
var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin"); // 提取公共部分插件
var HtmlWebpackPlugin = require('html-webpack-plugin'); // 自動(dòng)生成index.html頁(yè)面插件
var CopyWebpackPlugin = require('copy-webpack-plugin'); // 拷貝資源插件
var ExtractTextPlugin = require("extract-text-webpack-plugin"); // 抽離css樣式铺遂,通過(guò)require引入的css,會(huì)被webpack打包到j(luò)s中
var ENV = process.env.npm_lifecycle_event; // 直接通過(guò)獲取啟動(dòng)命令來(lái)判斷開發(fā)/生產(chǎn)環(huán)境
var isPro = process.env.NODE_ENV == "production";
var publicPath = "http://127.0.0.1:3000/dist/";
var webpackHotMiddleware = 'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=10000&reload=true';
var config = {
entry: {
vendor1 : ["es5-shim", "es5-sham", "console-polyfill", "babel-polyfill", webpackHotMiddleware],
vendor2 : ["jquery", "react", "react-dom", webpackHotMiddleware],
common: ["commonExt", webpackHotMiddleware],
bundle : ["register", webpackHotMiddleware]
},
output: {
path: path.resolve(__dirname, './src/dist'),
publicPath:publicPath,
filename: 'js/[name].js',
},
externals: {
// jQuery: 'window.$',
// 'react': 'React',
// 'react-dom': 'ReactDOM'
},
// devtool: 'eval-source-map',
module: {
// 默認(rèn)的require()方法會(huì)在webpack打包的時(shí)候去解壓茎刚,遍歷ReactJS及其依賴,
noParse: [
path.join(__dirname, "node_modules/react/dist/react.min.js"),
path.join(__dirname, "node_modules/react-dom/dist/react-dom.min.js"),
path.join(__dirname, "node_modules/jquery/dist/jquery.min.js"),
path.join(__dirname, "node_modules/babel-polyfill/dist/polyfill.min.js"),
path.join(__dirname, "node_modules/console-polyfill/index.js"),
path.join(__dirname, "node_modules/es5-shim/es5-shim.min.js"),
path.join(__dirname, "node_modules/es5-shim/es5-sham.min.js")
],
loaders: [
{
// es3ify-loader 兼容ie8襟锐,將es5轉(zhuǎn)譯成es3
test: /\.js$/,
exclude: /node_modules/,
loaders: ["es3ify-loader", "babel-loader"]
},
{
// 識(shí)別js中require引入的樣式表,并將其轉(zhuǎn)換和兼容處理
test: /\.(css|scss|sass)$/,
exclude: /node_modules/,
use: ['css-hot-loader'].concat(ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
options: { autoprefixer: true, sourceMap: true, importLoaders: 1 }
},
{
loader: 'postcss-loader',
options: {
sourceMap: true,
plugins: () => [autoprefixer({ browsers: ['last 2 versions', 'safari >= 5', 'ie 8', 'ie 9','iOS >= 7', 'Android >= 4.1'] })],
}
},
'sass-loader'
]
}))
},
{
// 圖片加載器膛锭,可以將較小的圖片轉(zhuǎn)成base64粮坞,減少http請(qǐng)求
// 如下配置蚊荣,將小于8kb的圖片轉(zhuǎn)成base64碼,大于8192byte的圖片將通過(guò)file-loader把源文件遷移到指定的路徑莫杈,并返回新的路徑
// [hash]/[name] hash值互例,防止重名,如兩張圖片一樣筝闹,只會(huì)生成同一hash
test: /\.(png|jpg|gif|svg)$/,
loader: [
'url-loader?limit=8192&name=images/[hash].[ext]',
'image-webpack-loader'
],
},
{
test: /\.(woff|woff2|eot|ttf)$/,
loader: 'file-loader?name=fonts/[name].[ext]',
},
]
},
resolve: {
extensions: ['*', '.js', '.jsx', ".css", ".scss"], // 用于自行補(bǔ)全文件后綴
alias: {
// 設(shè)置引入的插件別名或配置路徑
src : path.resolve(__dirname, "./src"),
public : path.resolve(__dirname, "./src/public"),
components: path.resolve(__dirname, "./src/public/components"),
react : path.join(__dirname, "node_modules/react/dist/react.min.js"),
"react-dom" : path.join(__dirname, "node_modules/react-dom/dist/react-dom.min.js"),
"babel-polyfill" : path.join(__dirname, "node_modules/babel-polyfill/dist/polyfill.min.js"),
"console-polyfill" : path.join(__dirname, "node_modules/console-polyfill/index.js"),
"es5-shim" : path.join(__dirname, "node_modules/es5-shim/es5-shim.min.js"),
"es5-sham" : path.join(__dirname, "node_modules/es5-shim/es5-sham.min.js"),
jquery : path.join(__dirname, "node_modules/jquery/dist/jquery.min.js"),
commonExt: path.resolve(__dirname, "./src/public/js/commonExt"),
register : path.resolve(__dirname, './src/public/components/register.js')
}
},
plugins: [
// 配置全局標(biāo)識(shí)
new webpack.DefinePlugin({
"process.env": { NODE_ENV: JSON.stringify(process.env.NODE_ENV || "development") }
}),
new webpack.LoaderOptionsPlugin({
debug: !isPro
}),
// 當(dāng)腳本有引入以下變量時(shí)媳叨,會(huì)自動(dòng)加載引入相對(duì)應(yīng)的模塊
new webpack.ProvidePlugin({
$: "jquery",// 一定要安裝低于2.0版本的,否則ie8會(huì)報(bào)錯(cuò)
jQuery: "jquery",
"window.jQuery": "jquery",
React: 'react',
ReactDOM: 'react-dom'
}),
// 抽離樣式
new ExtractTextPlugin({
filename: (getPath) => {
return getPath('css/style.css');
},
allChunks: true
}),
// 拷貝資源
new CopyWebpackPlugin(
[{
from: './src/public/plugins/',
to: 'plugins'
}]
),
// 提取被引用兩次以上的公共部分
// new CommonsChunkPlugin({
// name:"chunk",
// minChunks:2,
// // minChunks: Infinity //提取所有entry依賴模塊
// }),
new webpack.optimize.CommonsChunkPlugin({
name: 'chunk',
chunks: ['vendor2', 'bundle']
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin()
],
};
// 遍歷所有模板中的.html文件关顷,使用HtmlWebpackPlugin引入靜態(tài)資源(或者用ejs模板生成多個(gè))
var htmlfiles = fs.readdirSync(path.resolve(__dirname, "./src/public/templates"));
htmlfiles.forEach(function(item) {
var currentpath = path.resolve(__dirname+"/src/public/templates", item);
var extname = path.extname(currentpath);
if(fs.statSync(currentpath).isFile() && (extname == ".html" || extname == ".ejs")) {
var json = {
template: currentpath, // 設(shè)置引入的模板
filename: path.join(__dirname, './src/views/'+item), // 設(shè)置輸出路徑
inject: 'body', // 設(shè)置js插入位置
hash: true, // 為所有的資源加上hash值
chunks: ["style", "vendor1", "vendor2", "common", "chunk", "bundle"], // 指定引入的資源
showErrors: true // 是否展示錯(cuò)誤
}
// 生產(chǎn)環(huán)境壓縮html
if(isPro) {
json["minify"] = {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
}
}
config.plugins.push(
new HtmlWebpackPlugin(json)
);
}
});
if(isPro) {
config.plugins.push(
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
mangle: {
except: ['jQuery', '$', 'exports', 'require', "module"]
}
})
);
}
module.exports = config;