Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

webpack 简易配置入门教程 #12

Open
JZLeung opened this issue Aug 25, 2017 · 8 comments
Open

webpack 简易配置入门教程 #12

JZLeung opened this issue Aug 25, 2017 · 8 comments

Comments

@JZLeung
Copy link
Owner

JZLeung commented Aug 25, 2017

what-is-webpack
最近公司弄了个有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

    其中:

    • extract-text-webpack-plugin : 该插件主要为了抽离css样式,可以将css从打包的js中抽离出来。以link方式引入样式。
    • html-webpack-plugin : 该插件主要是用于生成html文件,并可以根据入口文件来引入相应的文件。
    • css-loader : 解析css文件中的 importrequire ,并处理他们。
    • style-loader : 将css样式通过 style 标签注入到html文件中。
    • file-loader : 指明webpack将所引入的对象,并返回一个公网能访问的url地址。
    • url-loader : 将文件转换成 base64编码。
  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!基本的配置就完成了。
    config1-ok
    我们可以看到,在项目中多了一个dist文件夹,里面存放的就是刚刚打包好的文件。打开index.html(需要在服务器中打开,并且服务器根目录为dist)可以看到,生成的html文件,征程显示我们写的内容,css样式则直接写入了style标签当中,而且自动引入了两个js文件,其中,vendors是带有公共部分的js文件,index则是我们一开始写的js逻辑文件。
    config1-result

@JZLeung
Copy link
Owner Author

JZLeung commented Aug 25, 2017

处理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');

打包测试:
config2-ok
config2-result

完美通过。

@JZLeung
Copy link
Owner Author

JZLeung commented Aug 25, 2017

处理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

重新编译打包:
config3-ok
config3-result

@JZLeung
Copy link
Owner Author

JZLeung commented Aug 25, 2017

使用 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));

来,打包看看:
config4-ok
config4-result
完美。

@JZLeung
Copy link
Owner Author

JZLeung commented Aug 25, 2017

配置多文件入口

项目有多个页面,每个页面都对应着一个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+'$');
    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

config5-ok

config5-result

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

@JZLeung
Copy link
Owner Author

JZLeung commented Aug 25, 2017

其他处理

  • 提取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'
            ]
        })
    }

    再次打包的结果:
    config6-ok

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


  • 每次打包旧的文件依旧存在
    每次执行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')。这样就方便多了。

@wzc0x0
Copy link

wzc0x0 commented Aug 25, 2017

处理scss文件。这里你少装了一个postcss-loader

@JZLeung
Copy link
Owner Author

JZLeung commented Aug 27, 2017

@wzc0x0 噢,那是因为装的是 postcss 的插件,所以没把postcss-loader算进去。

@JZLeung
Copy link
Owner Author

JZLeung commented Sep 1, 2017

新增仓库:webpack 多页面配置Demo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants