webpack+express+react系列一(webpack

作者: 冰Q | 来源:发表于2017-09-27 15:10 被阅读494次

    最近,发现自己离前端新技术接触越来越少了,由于用不上,连以前接触过的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;
    

    相关文章

      网友评论

        本文标题:webpack+express+react系列一(webpack

        本文链接:https://www.haomeiwen.com/subject/ictuextx.html