webpack+express+react系列一(webpack配置)

最近,发现自己离前端新技术接触越来越少了,由于用不上,连以前接触过的webpack、react这些都开始淡忘。本着一颗学习的心,打算在工作之余,抽取一部分时间来钻研,搭建一个简单的管理平台。
基于技术上的发展、稳定性及目前提倡的自动化和模块化开发,本人暂时确定采用webpack/express/react/react-router/redux。

前端自动化和模块化 -- 前端工具webpack的使用
  • 在开始之前,先看下项目的结构(由于项目采用的是express脚手架来生成的,后续添加的内容基本以此为基准)
项目目录结构

Paste_Image.png

public文件夹为前端开发目录,dist为打包后目录。

webpack配置。
  • views中的html采用的是插件HtmlWebpackPlugin生成的
// 遍历所有模板中的.html文件,使用HtmlWebpackPlugin引入静态资源(或者用ejs模板生成多个)
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, // 设置引入的模板
            filename: path.join(__dirname, './src/views/'+item), // 设置输出路径
            inject: 'body', // 设置js插入位置
            hash: true, // 为所有的资源加上hash值
            chunks: ["style", "vendor1", "vendor2", "common", "chunk", "bundle"], // 指定引入的资源
            showErrors: true // 是否展示错误
        }
        // 生产环境压缩html
        if(isPro) {
            json["minify"] = {
                removeComments: true,
                collapseWhitespace: true,
                removeAttributeQuotes: true
            }
        }
        config.plugins.push(
            new HtmlWebpackPlugin(json)
        );
    }
});
  • 设置环境变量process.env.NODE_ENV
// 在package.json中设置
"start": "set NODE_ENV=development && supervisor -i ./src/public ./src/bin/www"
  • 采用ExtractTextPlugin和postcss-loader处理样式。在js中采用require引入css样式表,webpack默认将其合并到编译后的文件中,故需要将其抽取出来,做转换和兼容处理。
## 在loader中添加
{
                // 识别js中require引入的样式表,并将其转换和兼容处理
                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对图片进行处理
{
      // 图片加载器,可以将较小的图片转成base64,减少http请求
      // 如下配置,将小于8kb的图片转成base64码,大于8192byte的图片将通过file-loader把源文件迁移到指定的路径,并返回新的路径
       // [hash]/[name] hash值,防止重名,如两张图片一样,只会生成同一hash
       test: /\.(png|jpg|gif|svg)$/,
       loader: [
                  'url-loader?limit=8192&name=images/[hash].[ext]',
                  'image-webpack-loader'
                ],
              
  • 对js进行转换处理
##  loader中添加
// 在新版中,loaders中的加载器,不能以"es3ify!babel"这样连起来,要么数组,要么"es3ify-loader!babel-loader"
{
      // es3ify-loader 兼容ie8,将es5转译成es3
      test: /\.js$/,
      exclude: /node_modules/,
      loaders: ["es3ify-loader", "babel-loader"]
}
  • 设置自动补全文件后缀
resolve: {
        extensions: ['*', '.js', '.jsx', ".css", ".scss"] // 值得注意的是,在新版中,数组中不能存在'',而是用'*'替代
} 
  • 使用插件CommonsChunkPlugin抽取公共依赖模块
new webpack.optimize.CommonsChunkPlugin({
     name: 'chunk',
    chunks: ['vendor2', 'bundle'] // 这里是output生成的输出文件
}),
  • 使用插件CopyWebpackPlugin来拷贝资源,主要用于第三方插件/库的拷贝,配合webpack的externals配置,可直接在html中引用cdn/本地第三方插件/库,而不会被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搭配
// 设置引入的插件别名,重定向到指定的文件,阻止webpack对require引用的文件进行遍历,查询其依赖等
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")
        }

// 设置自动引入相应模块功能
// 当脚本有引入以下变量时,会自动加载引入相对应的模块
 new webpack.ProvidePlugin({
            React: 'react',
            ReactDOM: 'react-dom'
  })

// 默认的require()方法会在webpack打包的时候去解压,遍历ReactJS及其依赖,使用noParse可阻止其默认行为
noParse: [
            path.join(__dirname, "node_modules/react/dist/react.min.js"),
            path.join(__dirname, "node_modules/react-dom/dist/react-dom.min.js")
        ]
热更新

作为一个会偷懒的前端,使用到了webpack,怎么能漏掉其热更新功能呢。webpack的热更新功能目前主要有两种配置方式,一种为webpack-dev-server,另一种为webpack-dev-middleware和webpack-hot-middleware两个中间件配合。其实webpack-dev-server也是采用webpack-dev-middleware这个中间件来实现的。由于本人后端采用的是node,直接忽略掉了webpack-dev-server。

  • 先装个逼
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中设置publicPath且publicPath必须为绝对路径
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();
}
  • 以上配置只能实现js的热更新,css要实现实时刷新效果,就必须引入css-hot-loader
结语

前奏基本已经搞定,剩下的就是一些优化了,不得不感慨前端工具发展之快,从grunt到gulp再到webpack,前端的打包工具不断的进化,改朝换代的速度让人不得不感慨。每种工具都学点,也懒得深入研究,一路挖坑一路填坑,回头想想,擦,谁的锅!!!
附上本人的完整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'); // 自动生成index.html页面插件
var CopyWebpackPlugin = require('copy-webpack-plugin'); // 拷贝资源插件
var ExtractTextPlugin = require("extract-text-webpack-plugin"); // 抽离css样式,通过require引入的css,会被webpack打包到js中
var ENV = process.env.npm_lifecycle_event; // 直接通过获取启动命令来判断开发/生产环境
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: {
        // 默认的require()方法会在webpack打包的时候去解压,遍历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转译成es3
                test: /\.js$/,
                exclude: /node_modules/,
                loaders: ["es3ify-loader", "babel-loader"]
            },
            {
                // 识别js中require引入的样式表,并将其转换和兼容处理
                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'
                    ]
                }))
            },
            {
                // 图片加载器,可以将较小的图片转成base64,减少http请求
                // 如下配置,将小于8kb的图片转成base64码,大于8192byte的图片将通过file-loader把源文件迁移到指定的路径,并返回新的路径
                // [hash]/[name] hash值,防止重名,如两张图片一样,只会生成同一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"], // 用于自行补全文件后缀
        alias: {
            // 设置引入的插件别名或配置路径
            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: [
        // 配置全局标识
        new webpack.DefinePlugin({ 
            "process.env": { NODE_ENV: JSON.stringify(process.env.NODE_ENV || "development") }
        }),
        new webpack.LoaderOptionsPlugin({
            debug: !isPro
        }),
        // 当脚本有引入以下变量时,会自动加载引入相对应的模块
        new webpack.ProvidePlugin({
            $: "jquery",// 一定要安装低于2.0版本的,否则ie8会报错
            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引入静态资源(或者用ejs模板生成多个)
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, // 设置引入的模板
            filename: path.join(__dirname, './src/views/'+item), // 设置输出路径
            inject: 'body', // 设置js插入位置
            hash: true, // 为所有的资源加上hash值
            chunks: ["style", "vendor1", "vendor2", "common", "chunk", "bundle"], // 指定引入的资源
            showErrors: true // 是否展示错误
        }
        // 生产环境压缩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;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,552评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,666评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,519评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,180评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,205评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,344评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,781评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,449评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,635评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,467评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,515评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,217评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,775评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,851评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,084评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,637评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,204评论 2 341

推荐阅读更多精彩内容