webpack2搭建开发环境

开发环境

本文主要介绍使用webpack2进行前端开发以及编译发布文件,主要涉及:

  • HMR - React
  • HMR - less
  • iconfont

如果你对 Vue 项目感兴趣,可移步至我的开源项目
buluoci-web

安装node依赖

第一步我们先搭建基础的webpack开发环境,并支持React Hot Module Replacement(HMR),为此,我们需要安装以下依赖到项目:

npm install --save-dev babel-core babel-loader babel-preset-es2015 webpack webpack-dev-server

react:

npm install --save-dev babel-preset-react react-hot-loader
npm install --save react react-dom

css:

npm install --save-dev style-loader css-loader postcss-loader autoprefixer

less:

npm install --save-dev less less-loader

图片:

npm install --save-dev image-webpack-loader file-loader url-loader

svgs转iconfont:

npm install --save-dev webfonts-loader

babel的配置文件

.bablrc

{
    "presets": [
        ["es2015", { "modules": false }],
        "react"
    ],
    "env": {
        "development": {
            "plugins": ["react-hot-loader/babel"]
        }
    }
}

我们只是在开发时才需要react HMR,所以react-hot-loader/babel 被配置到env为development下。

项目代码

src/index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="renderer" content="webkit" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
    <title></title>
</head>
<body>
    <div id="app"></div>
</body>
</html>

src/m.html

与index.html类似

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
    <meta name="format-detection" content="telephone=no" />
    <title></title>
</head>
<body>
    <div id="app"></div>
</body>
</html>

src/js/container/index.js

import React from 'react';
import { render } from 'react-dom';
import { AppContainer } from 'react-hot-loader';// necessary wrapper component for HMR

import App from 'js/views/Index';

function renderApp(Component) {
    render(<AppContainer><Component/></AppContainer>, document.getElementById('app'));
}
renderApp(App);

// Hot Module Replacement API
if(module.hot) module.hot.accept('js/views/Index', () => renderApp(App));

src/js/container/m.js

与index.js类似

import React from 'react';
import { render } from 'react-dom';
import { AppContainer } from 'react-hot-loader';// necessary wrapper component for HMR

import App from 'js/views/M';

function renderApp(Component) {
    render(<AppContainer><Component/></AppContainer>, document.getElementById('app'));
}
renderApp(App);

// Hot Module Replacement API
if(module.hot) module.hot.accept('js/views/M', () => renderApp(App));

如果有svg图标需要转字体

src/icons/index.js

module.exports = {
    files: ['./*.svg'],

    // https://github.com/nfroidure/svgicons2svgfont#api
    fontName: 'iconfont',
    fixedWidth: true,
    normalize: true,
    descent: 160,

    types: ["ttf"],
    formatOptions: {
        ttf: {
            // 保证输出一致
            ts: 1451512800000
        }
    }
};

这样配置后,src/icons/*svg 可以通过 import 'icons'; 以字体方式引入

src/js/views/Index.js

真正的index.html页面逻辑写在这里

import React from 'react';

import 'less/admin/index.less';// 如果有样式可以这样引入
import 'icons';// 如果需要,可以这样引入图标字体

export default function() {
    return (
        <div>这里的内容会填充到 index.html 的 div#app 里</div>
    );
}

src/js/views/M.js

与 Index.js 类似

import React from 'react';

export default function() {
    return (
        <div>这里的内容会填充到 m.html 的 div#app 里</div>
    );
}

开发环境的webpack配置

webpack.dev.config.js

var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var path = require('path');

var devPort = 8080;
var devHost = process.env.DEV_HOST || 'http://localhost';
var context = path.resolve('src');

module.exports = {
    context,
    entry: {
        index: [
            'react-hot-loader/patch',
            './js/container/index'
        ],
        m: [
            'react-hot-loader/patch',
            './js/container/m'
        ]
    },
    output: {
        filename: '[name].js',// for multi chunks

        // css启用sourcemap时需指定
        // 否则图片等相关资源在通过blob加载的css中无法正确引用
        publicPath: `${devHost}:${devPort}/`
    },
    plugins: [
        new webpack.NoEmitOnErrorsPlugin(),
        new webpack.NamedModulesPlugin(),
        new webpack.LoaderOptionsPlugin({
            options: {
                context,
                postcss: function() {
                    return [
                        require('autoprefixer')({ browsers: ["Android >= 4", "iOS >= 7", "IE >= 9"] })
                    ];
                },
            }
        }),

        new HtmlWebpackPlugin({
            template: 'index.html',
            favicon,
            chunks: ['index']
        }),
        new HtmlWebpackPlugin({
            template: 'm.html',
            favicon,
            chunks: ['m'],
            filename: 'm.html'
        })
    ],
    devServer: {
        noInfo: true,
        host: '0.0.0.0',
        port: devPort
    },
    devtool: 'eval',

    module: {
        rules: [
            {
                // iconfont
                test: /icons/,
                // embed解决 对publicPath解析bug
                use: [
                    'style-loader',
                    'css-loader',
                    'webfonts-loader'
                ]
            }, {
                // js and jsx
                test: /\.jsx?$/,
                exclude: /node_modules/,
                use: [
                    'babel-loader',// configed in .babelrc
                ]
            }, {
                // less
                test: /\.less$/,
                use: [
                    'style-loader',
                    'css-loader?sourceMap',
                    'postcss-loader',
                    'less-loader?sourceMap'
                ]
            }, {
                // images
                test: /\.(png|jpe?g)$/,
                use: ['file-loader']
            }
        ]
    },

    resolve: {
        extensions: ['*', '.js', '.jsx'],
        alias: {
            'js': path.resolve('src/js'),
            'less': path.resolve('src/less'),
            'img': path.resolve('src/img'),
            'icons': path.resolve('src/icons'),
        }
    }
};

万事俱备准备开发

package.json

{
  ...
  "scripts" : {
    "dev": "webpack-dev-server --hot --config webpack.dev.config.js"
  }
  ...
}

执行 npm run dev,然后打开浏览器前往 http://localhost:8080

生产环境

安装node依赖

npm install --save-dev extract-text-webpack-plugin chunk-manifest-webpack-plugin

生产环境的webpack配置

webpack.prod.config.js

var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var ChunkManifestPlugin = require('chunk-manifest-webpack-plugin');
var path = require("path");
var favicon = 'img/favicon.ico';
var context = path.resolve('src');

module.exports = {
    context,
    entry: {
        index: [
            './js/container/index'
        ],
        m: [
            './js/container/m'
        ],
        // 将公共库打包成libs.js
        libs: [
            'react',
            'react-dom',
        ]
    },
    output: {
        path: path.resolve('dist/assets'),
        filename: '[name].js?[chunkhash]'
    },
    plugins: [
        new webpack.LoaderOptionsPlugin({
            options: {
                context,
                postcss: function() {
                    return [
                        require('autoprefixer')({ browsers: ["Android >= 4", "iOS >= 7", "IE >= 9"] })
                    ];
                },
                imageWebpackLoader: {
                    mozjpeg: {
                        quality: 65
                    },
                    pngquant: {
                        quality: "65-90"
                    }
                }
            }
        }),
        new ChunkManifestPlugin(),// 防止chunk被hash更新
        new webpack.optimize.CommonsChunkPlugin({
            name: 'libs',
            filename: '[name].js?[chunkhash]',
            minChunks: Infinity
        }),

        // 将css输出到独立的文件,而不是内嵌在js中
        new ExtractTextPlugin('[name].css?[contenthash]'),

        new HtmlWebpackPlugin({
            template: 'index.html',
            favicon,
            chunks: ['home', 'libs']
        }),
        new HtmlWebpackPlugin({
            template: 'm.html',
            favicon,
            chunks: ['m', 'libs'],
            filename: 'm.html'
        })
    ],

    module: {
        loaders: [
            {
                // iconfont
                test: /icons/,
                use: ExtractTextPlugin.extract({
                    fallback: 'style-loader',
                    use: [
                        'css-loader',
                        'webfonts-loader?embed'
                    ]
                })
            }, {
                // js and jsx
                test: /\.jsx?$/,
                exclude: /node_modules/,
                use: [
                    'babel-loader',// configed in .babelrc
                ]
            }, {
                test: /\.less$/,
                // 输出独立的css文件
                use: ExtractTextPlugin.extract({
                    fallback: 'style-loader',
                    use: [
                        'css-loader?-minimize',
                        'postcss-loader',
                        'less-loader'
                    ]
                })
            }, {
                // images
                test: /\.(png|jpe?g)$/,
                use: [
                    'url-loader?limit=10000&name=[path][name].[ext]?[hash]',
                    'image-webpack-loader'
                ]
            }
        ]
    },

    resolve: {
        extensions: ['*', '.js', '.jsx'],
        alias: {
            'js': path.resolve('src/js'),
            'less': path.resolve('src/less'),
            'img': path.resolve('src/img'),
            'icons': path.resolve('src/icons'),
        }
    }
};

package.json

{
  ...
  "scripts" : {
    ...
    "clean": "rm -rf dist/assets",
    "build": "npm run clean && BABEL_ENV=production webpack -p --progress --config webpack.prod.config.js",
  }
  ...
}

执行 npm run build,文件会编译到 dist/assets下

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352

推荐阅读更多精彩内容

  • webpack 介绍 webpack 是什么 为什么引入新的打包工具 webpack 核心思想 webpack 安...
    yxsGert阅读 6,463评论 2 71
  • 无意中看到zhangwnag大佬分享的webpack教程感觉受益匪浅,特此分享以备自己日后查看,也希望更多的人看到...
    小小字符阅读 8,157评论 7 35
  • 1. 新建一个文件夹,命名为 webpack-cli , webpack-cli 就是你的项目名,项目名建议使用小...
    鲁大师666阅读 1,472评论 1 3
  • GitChat技术杂谈 前言 本文较长,为了节省你的阅读时间,在文前列写作思路如下: 什么是 webpack,它要...
    萧玄辞阅读 12,687评论 7 110
  • 写在开头 先说说为什么要写这篇文章, 最初的原因是组里的小朋友们看了webpack文档后, 表情都是这样的: (摘...
    Lefter阅读 5,285评论 4 31