webpack 简易配置入门教程

2017-10-31 阅读 1235 收藏 4 原链:segmentfault.com
分享到:

前端必备图书《JavaScript DOM编程艺术 第2版 》 >> >> 

最近公司弄了个有150+页面的项目,心想,终于有机会可以去学习webpack了,以前想学却没有实际项目去引导逼迫去学,现在终于领略到了webpack的强大了。

什么是webpack

在前端的项目开发中,总有大量的页面和样式需要处理,而维护这些文件也成了头疼的问题。

为了简化开发,于是就有很多好的开发方式,如:

  • 模块化开发。每个功能模块都分开成一个个独立的组件,需要的时候再引入。

  • scss等预处理器。

  • 使用pug, jade 更快编写HTML。

这些方式确实可以大大提高开发效率,但是每种方式都有自己的打包方式,还有兼容性处理,如果纯手动处理,必然会增加工作量。

但是,现在有了webpack,上述问题基本解决了,一个webpack就可以处理各种繁琐的过程,给你一个清爽,快速的开发环境。

WebPack可以看做是模块打包机:它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其转换和打包为合适的格式供浏览器使用。

简单的来说,在项目开发中,

  • 你可以使用js,es6/7,甚至ts开发。

  • es6/es7, .ts => .js

  • 使用stylus, scss, less 等预处理器编写css

  • .scss/.less => .css

webpack可以将你项目中的所有文件,处理成浏览器能识别的文件。

开始使用webpack

先新建一个练手用的空文件夹 $ mkdir webpack-demo ,并进入该文件夹。

  1. 新建 package.json.

    $ npm init
    
  2. 安装webpack及其基本插件。

    $ npm i -D webpack extract-text-webpack-plugin html-webpack-plugin css-loader file-loader style-loader url-loader
    

    其中:

  3. 配置webpack。

    新建一个配置文件: $ touch webpack.config.js

    var webpack = require('webpack');
    var path = require('path');
    
    var ExtractTextPlugin = require("extract-text-webpack-plugin");
    var HtmlWebpackPlugin = require('html-webpack-plugin');
    
    var webpackConfig = {
        // 设置入口文件。
        entry: './src/js/index.js',
        output: {
            // 设置输出文件夹
            path: path.join(__dirname, 'dist'),
            // 设置公用文件夹路径
            publicPath: '/',
            // 设置输出的js文件的名字规则。
            // [name] 为chunk中的名称
            // [hash] 为webpack生成的哈希值
            filename: "js/[name].[hash].bundle.js"
        },
        module: {
            rules: [{
                // 处理css文件
                test: /\.css$/,
                loader: "style-loader!css-loader"
            }, {
                // 处理html文件,并处理img 中 src 和 data-src 的引入路径
                test: /\.html$/,
                loader: "html-loader?attrs=img:src img:data-src"
            }, {
                // 处理字体文件
                test: /\.(woff|woff2|ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
                loader: 'file-loader?name=./fonts/[name].[ext]'
            }, {
                // 处理图片,并将8k以下的图片转为base64编码
                test: /\.(png|jpg|gif)$/,
                loader: 'url-loader?limit=8192&name=./img/[hash].[ext]'
            }]
        },
        plugins: [
            // 公共js提取
            new webpack.optimize.CommonsChunkPlugin({
                name: 'vendors', // 将公共模块提取,生成名为`vendors`的chunk
                // minChunks: 3 // 提取至少3个模块共有的部分
            }),
            // 提取公共css样式
            new ExtractTextPlugin('./css/[name].css'),
            // 处理html文件。
            new HtmlWebpackPlugin({
                filename: './view/index.html', //生成的html存放路径,相对于path
                template: './src/view/index.html', //html模板路径
                inject: 'body', //js插入的位置,true/'head'/'body'/false
                hash: true, //为静态资源生成hash值
                // chunks: ['vendors', allDirs[i] + '/' + matches[1]], //需要引入的chunk,不配置就会引入所有页面的资源
                minify: { //压缩HTML文件
                    removeComments: true, //移除HTML中的注释
                    collapseWhitespace: false //删除空白符与换行符
                }
            })
        ],
        // 设置开发服务器
        devServer: {
            contentBase: path.join(__dirname, "dist/"),
            host: 'localhost',
            port: 9090,
            inline: true
        }
    }
    
    module.exports = webpackConfig
    
  4. 测试配置文件: 先设置开发文件夹目录:

    -   webpack-demo
        +   node_modules
        -   src
            +   js      // 存放js文件
            +   css     // 存放css样式
            +   view    // 存放模板文件
    
            webpack.config.js
            package.json
    

    新建一个 html 文件: $ touch ./src/view/index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Hello World</title>
    </head>
    <body>
        <h2>Hello World</h2>
        <p>This is from webpack-demo</p>
    </body>
    </html>
    

    新建css文件: $ touch ./src/css/index.css

    h2{
        color: red;
        opacity: 0.5;
        transform: rotateZ(-10deg);
    }
    p{
        color: green;
    }
    

    新建js文件: $ touch ./src/js/index.js

    // 引入css文件。
    require('../css/index.css')
    
    console.log('hello world');
    

    测试打包: $ webpack OK!基本的配置就完成了。 我们可以看到,在项目中多了一个dist文件夹,里面存放的就是刚刚打包好的文件。打开index.html(需要在服务器中打开,并且服务器根目录为dist)可以看到,生成的html文件,征程显示我们写的内容,css样式则直接写入了style标签当中,而且自动引入了两个js文件,其中,vendors是带有公共部分的js文件,index则是我们一开始写的js逻辑文件。

处理scss文件。

既然都用到了webpack自动打包了,那也顺便使用webpack去处理scss文件(个人爱好是scss,less的处理同理),顺便也处理完css中的其他兼容性问题和浏览器前缀问题吧(程序员的思维就是,懒)。

现在css中需要处理的有:

  1. 浏览器前缀和大部分兼容性问题: autoprefixer

  2. flex 的兼容性问题: postcss-flexibility

  3. opacity 兼容IE: postcss-opacity

  4. 颜色兼容性问题: postcss-color-rgba-fallback

  5. scss文件处理: sass-loader 同时需要依赖 node-sass

  6. 压缩css文件: cssnano

安装上书postcss-loader的插件: $ npm i -D autoprefixer postcss-flexibility postcss-opacity postcss-color-rgba-fallback sass-loader node-sass

现在在webpack中处理css的问题,基本都是通过一个 postcss-loader 去完成所有的处理问题。

先新建一个文件夹,用于存放所有的scss文件: $ mkdir ./src/scss

再新建一个index.scss, $ touch ./src/scss/index.scss

body{
    background: black;
    color: white;
    h2{
        transform: translateX(10px) rotateZ(-10deg);
        color: red;
        opacity: 0.5;
    }
}

将postcss的加载器中需要的单独提取出来放在一个配置文件中: $ touch postcss.config.js

module.exports = {
    plugins: [
        // minify css
        require('cssnano')({
            preset: 'default'
        }),
        // 处理css前缀
        require('autoprefixer')({
            browserslist: [
                "> 1%",
                "last 2 versions",
                "Edge",
                "ie >= 9"
            ]
        }),
        // 处理flex浏览器兼容性
        require('postcss-flexibility'),
        // 处理css中rgba颜色代码
        require('postcss-color-rgba-fallback'),
        // 处理css中opacity的IE兼容性。
        require('postcss-opacity')
    ]
}

修改 webpack.config.js:

var webpackConfig = {
    ...
    module: {
        rules: [{
            // 处理css文件
            test: /\.(scss|sass|css)$/,
            // loader: "css-loader?importLoaders=1!postcss-loader!sass-loader",
            // loader执行顺序是从右到左:sass-loader -> postcss-loader -> css-loader
            use: [
                "style-loader",
                {
                    loader: "css-loader",
                    options: {
                        // // 0 => no loaders (default); 1 => postcss-loader; 2 => postcss-loader, sass-loader
                        importLoaders: 2
                    }
                },
                'postcss-loader',
                'sass-loader'
            ]
        },
        ...
        ]
    }
    ...
}

module.exports = webpackConfig

修改一下入口文件中的样式引入: $ vim ./src/js/index.js

require('../scss/index.scss')

console.log('hello world');

打包测试:

完美通过。

处理pug/jade文件

项目有很多的页面,而且页面之间也有很多相同的页面,之前写惯了vue的组件,所以注册我们也引入了pug作为前端模板引擎。 pug 的前身就是 jade ,所以语法什么都得基本都是一致的。具体的看 官网

和处理css的一样,先要安装加载器。 $ npm i -D pug pug-loader

新建一个简单的页面: $ touch ./src/view/index.pug

html
    title   Test.html
body
    h2  Welcome to pug.
    p   This is from index.pug

修改一下配置文件中的html模板入口:

var webpackConfig = {
    ...
    plugins: [
        // 公共js提取
        new webpack.optimize.CommonsChunkPlugin({
            name: 'vendors', // 将公共模块提取,生成名为`vendors`的chunk
        }),
        // 提取公共css样式
        new ExtractTextPlugin('./css/[name].css'),
        // 处理html文件。
        new HtmlWebpackPlugin({
            filename: './view/index.html', //生成的html存放路径,相对于path
            template: './src/view/index.pug', //html模板路径
            inject: 'body', //js插入的位置,true/'head'/'body'/false
            hash: true, //为静态资源生成hash值
            chunks: ['vendors', 'index'], //需要引入的chunk,不配置就会引入所有页面的资源
            minify: { //压缩HTML文件
                removeComments: true, //移除HTML中的注释
                collapseWhitespace: false //删除空白符与换行符
            }
        })
    ],
    ...
}

module.exports = webpackConfig

重新编译打包:

使用 babel 来“编译”你的js

在项目开发中,难免会遇到使用 es6 ,甚至 es7 去编写js。但是大部分浏览器却不支持这些语法,这时候,就需要 babel-loader 来处理js,并将其转换为浏览器能识别的 es5 语法。

还是要安装依赖: $ npm i -D babel-loader babel-core babel-preset-es2015

如果用上了es7 的语法,就要根据不同阶段语法提案的转码规则(共有4个阶段),选装一个:

$ npm i -D babel-preset-stage-0
$ npm i -D babel-preset-stage-1
$ npm i -D babel-preset-stage-2
$ npm i -D babel-preset-stage-3

新建一个 .babelrc babel配置文件: $ touch .babelrc

{
    "presets": ["es2015"],
    "plugins": []
}

修改 webpack 配置文件:

var webpackConfig = {
    ...
    module: {
        rules: [{
            test: /\.js$/,
            // 不编译 node_modules 下的文件
            exclude: /node_modules/,
            loader: "babel-loader"
        }]
    }
    ...
}

module.exports = webpackConfig

在 index.js 中写个 es6 的语法:

require('../scss/index.scss')

console.log('hello world');

((message) => {
    console.log(message);
})('message from es6')

let step = 2;
var add = number => number*=step

console.log(add(2));

来,打包看看: 完美。

配置多文件入口

项目有多个页面,每个页面都对应着一个js入口和一个页面,但是,入口 entry 和 html-webpack-plugin 每次只能配置一个具体的入口文件,如果每增加一个页面就要去配置,那么这无形之中增加了工作量,因此,我们需要一个通用的入口文件。

既然在node中可以访问文件夹并读取其中的文件名,那么我们可以使用node来去循环读取文件夹下的js文件,并自动添加至入口配置中:

新建一个config 文件夹,以存放webpack可变的配置,方便以后修改: $ mkdir config

因为遍历入口文件都是一样的操作流程,所以先写一个工具包:$ touch config/utils.js

var fs = require('fs');
// 递归遍历文件夹,获取入口文件
function getAllFiles(dirRoot, type){
    var filterReg = new RegExp('.'+type+'/div>);
    function getAllFileFromDir(root) {
        var res = [], files = fs.readdirSync(root)
        files.forEach((file) => {
            var pathname = root+'/'+file,
                state = fs.lstatSync(pathname)
            if (!state.isDirectory()) {
                // 过滤相对应的文件
                filterReg.test(pathname) && res.push(pathname)
                // res.push(pathname.replace(dir_root+'/', ''))
            }else{
                res = res.concat(getAllFileFromDir(pathname))
            }
        })
        return res
    }
    return getAllFileFromDir(dirRoot)
}

function getEntry(files, replaces){
    var entry = {}
    for (var i = 0; i < files.length; i++) {
        var filename = files[i]
        replaces.map((replace) => {
            filename = filename.replace(replace, '')
        })
        entry[filename] = files[i]
    }
    return entry
}

module.exports = {
    getAllFiles,
    getEntry
}

再新建一个 js 入口文件配置: $ touch config/webpack.entry.js

var path = require('path');
var utils = require('./utils.js')
var dir_root = path.resolve(__dirname, '../src/js');

// console.log(getAllFiles(dir_root));
var allFiles = utils.getAllFiles(dir_root, 'js')
var entry = utils.getEntry(allFiles, ['js', dir_root+'/'])
console.log(entry);
module.exports = entry

同样的,页面入口文件也添加一个: $ touch config/webpack.plugins.js

var webpack = require('webpack')
var path = require('path');
var utils = require('./utils.js')

var ExtractTextPlugin = require("extract-text-webpack-plugin");
var HtmlWebpackPlugin = require('html-webpack-plugin');

var entry = require('./webpack.entry.js')

var plugins = [
    // 公共js提取
    new webpack.optimize.CommonsChunkPlugin({
        name: 'vendors', // 将公共模块提取,生成名为`vendors`的chunk
        minChunks: 3 // 提取至少3个模块共有的部分
    }),
    // 提取公共css样式
    new ExtractTextPlugin('./css/[name].css'),
]
let dir_root = path.resolve(__dirname, '../src/view');
var pugFiles = utils.getAllFiles(dir_root, 'pug')

pugFiles = utils.getEntry(pugFiles, ['.pug', dir_root+'/'])

for (var key in pugFiles) {
    if (pugFiles.hasOwnProperty(key)) {
        let opt = {
            filename: './view/'+ key +'.html',
            template: pugFiles[key],
            hash: true,
            minify: { //压缩HTML文件
                removeComments: true, //移除HTML中的注释
                collapseWhitespace: false //删除空白符与换行符
            }
        }
        if (entry.hasOwnProperty(key)) {
            opt['chunks'] = ['vendors', key]
            opt['inject']= 'body'
        }
        console.log(opt);
        plugins.push(new HtmlWebpackPlugin(opt))
    }
}

module.exports = plugins

再次修改webpack的配置:

var entry = require('./config/webpack.entry.js')
var plugins = require('./config/webpack.plugins.js')

var webpackConfig = {
    entry,
    plugins,
    ...
}

module.exports = webpackConfig

可以看到,dist文件夹下,多了几个html文件和js文件。

其他处理

  • 提取css到外部link,而非style标签 这个解决办法比较简单,只需要用 extract-text-webpack-plugin 插件将css提取出来即可:

    {
        test: /\.(scss|sass|css)$/,
        use: ExtractTextPlugin.extract({
            fallback: "style-loader",
            use: [{
                    loader: "css-loader",
                    options: {
                        // // 0 => no loaders (default); 1 => postcss-loader; 2 => postcss-loader, sass-loader
                        importLoaders: 2
                    }
                },
                'postcss-loader',
                'sass-loader'
            ]
        })
    }
    

    再次打包的结果:

    列出dis文件夹的目录树可以发现,多了一个css的文件夹。打开页面可以发现,页面中的style标签已经被link替代了。

  • 每次打包旧的文件依旧存在 每次执行webpack打包(当然在实际开发中应该使用热更新服务器,不需要频繁打包)都会产生一堆新的js文件。

    解决办法:

    一是将打包的js文件的hash值去掉,这样就会新的打包js文件,将旧的覆盖。

    二是使用 clean-webpack-plugin

    var CleanWebpackPlugin = require('clean-webpack-plugin');
    var plugins = [
        // 每次打包前都清空dist文件
        new CleanWebpackPlugin(['dist'], {
            root: path.resolve(__dirname, '../')
        }),
        // 公共js提取
        new webpack.optimize.CommonsChunkPlugin({
            name: 'vendors', // 将公共模块提取,生成名为`vendors`的chunk
            // minChunks: 3 // 提取至少3个模块共有的部分
        }),
        // 提取公共css样式
        new ExtractTextPlugin('./css/[name].css'),
    ]
    
  • 批量新建文件 因为每个页面基本都有3个文件:page.pug, page.js, page.scss,如果每次新加一个页面都要手动新建3个文件的话,效率太低下,能用命令行解决的,当然要用命令行解决啦。

    先写个小脚本 newpage.sh:

    #!/bin/sh
    if [[ $1 ]]; then
        filename=$1
        touch ./src/js/$filename.js
        touch ./src/view/$filename.pug
        touch ./src/scss/$filename.scss
    fi
    

    使用方式就是: $ ./newpage.sh filename,要运行该脚本,就需要先将脚本设置为可执行: $ sudo chmod +x ./newpage.sh

    还可以将它写进 npm 的 package.json 的命令中,则命令可以变为:npm run new -- filename

    {
        "script": {
            "new": "./newpage.sh"
        }
    }
    
  • 定义路径常量 在编写js时,因为有可能js文件是在很多级的目录当中,如果每次都使用 .. 来定位上一层目录的话,那么这个定位就会十分繁琐。这时可以使用 webpack 提供的 resolve.alias 配置来使引入文件的时候变得更加方便简单。

    resolve: {
        alias: {
            scss: path.resolve(__dirname, 'src/scss'),
            js: path.resolve(__dirname, 'src/js'),
            view: path.resolve(__dirname, 'src/view'),
            assets: path.resolve(__dirname, 'src/assets')
        }
    }
    

    这样,在 other.js 中,就可以直接使用 require('scss/about.scss') 而不需要写繁琐的 require('../../scss/about.scss')。这样就方便多了。

回到顶部